diff options
| author | vitya-smirnov <[email protected]> | 2025-08-26 16:00:33 +0300 |
|---|---|---|
| committer | vitya-smirnov <[email protected]> | 2025-08-26 16:25:59 +0300 |
| commit | b142a8ffb787fc94482c7504ff7fb0ed820a4a28 (patch) | |
| tree | 3542d763492a14cc6bd279870ed6932bd560e5d1 | |
| parent | 6c7504ede1e3ce47b5da317e81256c47b75c79e4 (diff) | |
YQL-19747: Replicate unambiguous columns and fix ranking
This is an improvement for column suggestions.
Even when user made a source aliased, he still is
able to reference a column with both unqualified
and qualified name, so we should provide both
candidates, but with unqualified more relevant.
As this patch required non trivial `NameResponse`
modifications at the `CompletionEngine`, ranking
should be used properly, so a way to pass it to
the engine was introduced.
commit_hash:1f2b90e2f6fbb7d33d9fc8479f43349a7f08c320
8 files changed, 263 insertions, 40 deletions
diff --git a/yql/essentials/sql/v1/complete/name/service/column/replicating.cpp b/yql/essentials/sql/v1/complete/name/service/column/replicating.cpp new file mode 100644 index 00000000000..ef2d39969d5 --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/column/replicating.cpp @@ -0,0 +1,85 @@ +#include "replicating.h" + +#include <yql/essentials/sql/v1/complete/name/object/simple/static/schema.h> + +#include <library/cpp/case_insensitive_string/case_insensitive_string.h> + +#include <util/generic/hash.h> + +namespace NSQLComplete { + + namespace { + + class TNameService: public INameService { + public: + TNameService(INameService::TPtr origin, IRanking::TPtr ranking) + : Origin_(std::move(origin)) + , Ranking_(std::move(ranking)) + { + } + + NThreading::TFuture<TNameResponse> Lookup(const TNameRequest& request) const final { + NThreading::TFuture<TNameResponse> response = Origin_->Lookup(request); + + if (!request.Constraints.Column || request.Constraints.Column->TableAlias) { + return response; + } + + return std::move(response).Apply([request, ranking = Ranking_](auto f) -> TNameResponse { + TNameResponse response = f.ExtractValue(); + + TVector<TGenericName> replicatable = ReplicatableColumns(response.RankedNames, request.Prefix); + std::ranges::move(std::move(replicatable), std::back_inserter(response.RankedNames)); + + ranking->CropToSortedPrefix(response.RankedNames, request.Constraints, request.Limit); + + return response; + }); + } + + private: + static TVector<TGenericName> ReplicatableColumns(const TVector<TGenericName>& names, TStringBuf prefix) { + THashMap<TString, size_t> references; + for (const TGenericName& name : names) { + if (!std::holds_alternative<TColumnName>(name)) { + continue; + } + + const TColumnName& column = std::get<TColumnName>(name); + if (column.TableAlias.empty()) { + continue; + } + + references[column.Identifier] += 1; + } + + TVector<TGenericName> replicatable; + for (auto& [column, count] : references) { + if (count != 1) { + continue; + } + + // TODO(YQL-19747): introduce a single source of truth of filtration policy + if (!TCaseInsensitiveStringBuf(column).StartsWith(prefix)) { + continue; + } + + TColumnName name; + name.Identifier = column; + replicatable.emplace_back(std::move(name)); + } + + return replicatable; + } + + INameService::TPtr Origin_; + IRanking::TPtr Ranking_; + }; + + } // namespace + + INameService::TPtr MakeColumnReplicatingService(INameService::TPtr origin, IRanking::TPtr ranking) { + return new TNameService(std::move(origin), std::move(ranking)); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/column/replicating.h b/yql/essentials/sql/v1/complete/name/service/column/replicating.h new file mode 100644 index 00000000000..120d08cc264 --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/column/replicating.h @@ -0,0 +1,10 @@ +#pragma once + +#include <yql/essentials/sql/v1/complete/name/service/ranking/ranking.h> +#include <yql/essentials/sql/v1/complete/name/service/name_service.h> + +namespace NSQLComplete { + + INameService::TPtr MakeColumnReplicatingService(INameService::TPtr origin, IRanking::TPtr ranking); + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/column/ya.make b/yql/essentials/sql/v1/complete/name/service/column/ya.make index 64c11175e2f..26d6e7b8e2d 100644 --- a/yql/essentials/sql/v1/complete/name/service/column/ya.make +++ b/yql/essentials/sql/v1/complete/name/service/column/ya.make @@ -2,6 +2,7 @@ LIBRARY() SRCS( name_service.cpp + replicating.cpp ) PEERDIR( 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 d8bcf5aaeb0..9b3d01e2fab 100644 --- a/yql/essentials/sql/v1/complete/name/service/name_service.h +++ b/yql/essentials/sql/v1/complete/name/service/name_service.h @@ -74,6 +74,7 @@ namespace NSQLComplete { struct TColumnName: TIdentifier { struct TConstraints { + TString TableAlias; TVector<TAliased<TTableId>> Tables; THashMap<TString, THashSet<TString>> WithoutByTableAlias; }; diff --git a/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp b/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp index 032b2a858f1..2e00224f768 100644 --- a/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp +++ b/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp @@ -59,7 +59,7 @@ namespace NSQLComplete { return std::visit([this](const auto& name) -> size_t { using T = std::decay_t<decltype(name)>; - auto content = NormalizeName(ContentView(name)); + TString content = NormalizeName(ContentView(name).first); if constexpr (std::is_same_v<T, TKeyword>) { if (auto weight = Frequency_.Keywords.FindPtr(content)) { @@ -97,6 +97,10 @@ namespace NSQLComplete { return std::numeric_limits<size_t>::max(); } + if constexpr (std::is_same_v<T, TBindingName>) { + return std::numeric_limits<size_t>::max() - 4; + } + if constexpr (std::is_same_v<T, TClusterName>) { return std::numeric_limits<size_t>::max() - 8; } @@ -109,17 +113,20 @@ namespace NSQLComplete { return std::numeric_limits<size_t>::max() - weight; } - TStringBuf ContentView(const TGenericName& name Y_LIFETIME_BOUND) const { - return std::visit([](const auto& name) -> TStringBuf { + std::pair<TStringBuf, TStringBuf> ContentView(const TGenericName& name Y_LIFETIME_BOUND) const { + return std::visit([](const auto& name) -> std::pair<TStringBuf, TStringBuf> { using T = std::decay_t<decltype(name)>; if constexpr (std::is_base_of_v<TKeyword, T>) { - return name.Content; + return {name.Content, ""}; + } + if constexpr (std::is_base_of_v<TColumnName, T>) { + return {name.TableAlias, name.Identifier}; } if constexpr (std::is_base_of_v<TIdentifier, T>) { - return name.Identifier; + return {name.Identifier, ""}; } if constexpr (std::is_base_of_v<TUnknownName, T>) { - return name.Content; + return {name.Content, ""}; } }, name); } diff --git a/yql/essentials/sql/v1/complete/sql_complete.cpp b/yql/essentials/sql/v1/complete/sql_complete.cpp index 23b62f7d0a8..fc8db52af13 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete.cpp @@ -8,6 +8,7 @@ #include <yql/essentials/sql/v1/complete/name/service/ranking/dummy.h> #include <yql/essentials/sql/v1/complete/name/service/binding/name_service.h> #include <yql/essentials/sql/v1/complete/name/service/column/name_service.h> +#include <yql/essentials/sql/v1/complete/name/service/column/replicating.h> #include <yql/essentials/sql/v1/complete/name/service/schema/name_service.h> #include <yql/essentials/sql/v1/complete/name/service/static/name_service.h> #include <yql/essentials/sql/v1/complete/name/service/union/name_service.h> @@ -33,6 +34,7 @@ namespace NSQLComplete { TSqlCompletionEngine( TLexerSupplier lexer, INameService::TPtr names, + IRanking::TPtr ranking, TConfiguration configuration) : Configuration_(std::move(configuration)) , SyntaxAnalysis_(MakeLocalSyntaxAnalysis( @@ -42,7 +44,9 @@ namespace NSQLComplete { Configuration_.ForcedPreviousByToken_)) , GlobalAnalysis_(MakeGlobalAnalysis()) , Names_(std::move(names)) + , Ranking_(std::move(ranking)) { + Ranking_ = Ranking_ ? Ranking_ : MakeDummyRanking(); } NThreading::TFuture<TCompletion> @@ -87,7 +91,12 @@ namespace NSQLComplete { children.emplace_back(Names_); } - return MakeUnionNameService(std::move(children), MakeDummyRanking()) + INameService::TPtr service = + MakeColumnReplicatingService( + MakeUnionNameService(std::move(children), Ranking_), + Ranking_); + + return service ->Lookup(std::move(request)) .Apply([this, input, local = std::move(local)](auto f) { return ToCompletion(input, std::move(local), f.ExtractValue()); @@ -165,6 +174,7 @@ namespace NSQLComplete { table = !table->empty() ? table : Nothing(); request.Constraints.Column = TColumnName::TConstraints(); + request.Constraints.Column->TableAlias = local.Column->Table; request.Constraints.Column->Tables = TColumnContext(*global.Column).ExtractAliased(table).Tables; request.Constraints.Column->WithoutByTableAlias = global.Column->WithoutByTableAlias; @@ -231,14 +241,16 @@ namespace NSQLComplete { ILocalSyntaxAnalysis::TPtr SyntaxAnalysis_; IGlobalAnalysis::TPtr GlobalAnalysis_; INameService::TPtr Names_; + IRanking::TPtr Ranking_; }; ISqlCompletionEngine::TPtr MakeSqlCompletionEngine( TLexerSupplier lexer, INameService::TPtr names, - TConfiguration configuration) { + TConfiguration configuration, + IRanking::TPtr ranking) { return MakeHolder<TSqlCompletionEngine>( - lexer, std::move(names), std::move(configuration)); + lexer, std::move(names), std::move(ranking), std::move(configuration)); } } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/sql_complete.h b/yql/essentials/sql/v1/complete/sql_complete.h index a347134e2f5..8fb02c37b06 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.h +++ b/yql/essentials/sql/v1/complete/sql_complete.h @@ -4,6 +4,7 @@ #include <yql/essentials/sql/v1/complete/core/input.h> #include <yql/essentials/sql/v1/complete/core/environment.h> +#include <yql/essentials/sql/v1/complete/name/service/ranking/ranking.h> #include <yql/essentials/sql/v1/complete/name/service/name_service.h> #include <yql/essentials/sql/v1/lexer/lexer.h> @@ -70,6 +71,7 @@ namespace NSQLComplete { ISqlCompletionEngine::TPtr MakeSqlCompletionEngine( TLexerSupplier lexer, INameService::TPtr names, - TConfiguration configuration = {}); + TConfiguration configuration = {}, + IRanking::TPtr ranking = nullptr); } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp index 7ee10977a6a..52eb2033717 100644 --- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp @@ -155,6 +155,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { TVector<TString> clusters(begin(clustersIt), end(clustersIt)); TFrequencyData frequency; + IRanking::TPtr ranking = MakeDefaultRanking(frequency); TVector<INameService::TPtr> children = { MakeStaticNameService(std::move(names), frequency), @@ -166,10 +167,10 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { MakeClusterNameService( MakeStaticClusterDiscovery(std::move(clusters)))), }; - INameService::TPtr service = MakeUnionNameService( - std::move(children), MakeDefaultRanking(frequency)); + INameService::TPtr service = MakeUnionNameService(std::move(children), ranking); - return MakeSqlCompletionEngine(std::move(lexer), std::move(service)); + TConfiguration config; + return MakeSqlCompletionEngine(std::move(lexer), std::move(service), config, ranking); } TVector<TCandidate> Complete(ISqlCompletionEngine::TPtr& engine, TString sharped, TEnvironment env = {}) { @@ -256,8 +257,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { }; TVector<TCandidate> expected = { - {BindingName, "$cluster_name"}, {TableName, "`maxim`"}, + {BindingName, "$cluster_name"}, {ClusterName, "example"}, {ClusterName, "loggy"}, {ClusterName, "saurus"}, @@ -761,8 +762,8 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } { TVector<TCandidate> expected = { - {BindingName, "$hello"}, {TableName, "`maxim`"}, + {BindingName, "$hello"}, {ClusterName, "example"}, {ClusterName, "loggy"}, {ClusterName, "saurus"}, @@ -778,9 +779,9 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } { TVector<TCandidate> expected = { - {BindingName, "$action"}, {TableName, "`people`"}, {FolderName, "`yql/`", 1}, + {BindingName, "$action"}, {ClusterName, "example"}, {ClusterName, "loggy"}, {ClusterName, "saurus"}, @@ -1259,17 +1260,19 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } { TVector<TCandidate> expected = { + {ColumnName, "Age"}, + {ColumnName, "Name"}, {ColumnName, "x.Age"}, {ColumnName, "x.Name"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "SELECT # FROM example.`/people` AS x"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, "SELECT # FROM example.`/people` AS x"), expected); } { TVector<TCandidate> expected = { {ColumnName, "Age"}, {ColumnName, "Name"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "SELECT x.# FROM example.`/people` AS x"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(3, engine, "SELECT x.# FROM example.`/people` AS x"), expected); } { TVector<TCandidate> expected = { @@ -1293,13 +1296,18 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )"; TVector<TCandidate> expected = { + {ColumnName, "Age"}, + {ColumnName, "Name"}, + {ColumnName, "course"}, + {ColumnName, "room"}, + {ColumnName, "time"}, {ColumnName, "ep.Age"}, {ColumnName, "ep.Name"}, {ColumnName, "et.course"}, {ColumnName, "et.room"}, {ColumnName, "et.time"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(5, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } } @@ -1313,13 +1321,17 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )"; TVector<TCandidate> expected = { - {ColumnName, "et.Room"}, - {ColumnName, "et.time"}, + {ColumnName, "Age"}, + {ColumnName, "Name"}, + {ColumnName, "Room"}, + {ColumnName, "time"}, {ColumnName, "ep.Age"}, {ColumnName, "ep.Name"}, + {ColumnName, "et.Room"}, + {ColumnName, "et.time"}, {Keyword, "ALL"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(5, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } { TString query = R"( @@ -1329,15 +1341,15 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )"; TVector<TCandidate> expected = { - {ColumnName, "y.course"}, {ColumnName, "x.course"}, {ColumnName, "x.room"}, - {ColumnName, "y.room"}, {ColumnName, "x.time"}, + {ColumnName, "y.course"}, + {ColumnName, "y.room"}, {ColumnName, "y.time"}, {Keyword, "ALL"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(7, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } { TString query = "SELECT # FROM (SELECT 1 AS x)"; @@ -1359,12 +1371,15 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )"; TVector<TCandidate> expected = { - {ColumnName, "ep.test"}, + {ColumnName, "Age"}, + {ColumnName, "Name"}, + {ColumnName, "test"}, {ColumnName, "ep.Age"}, {ColumnName, "ep.Name"}, + {ColumnName, "ep.test"}, {Keyword, "ALL"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } { TString query = R"( @@ -1381,14 +1396,17 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )"; TVector<TCandidate> expected = { - {ColumnName, "x.Age"}, - {ColumnName, "x.Name"}, + {ColumnName, "Age"}, + {ColumnName, "room"}, + {ColumnName, "time"}, {ColumnName, "ep.Name"}, {ColumnName, "ep.room"}, {ColumnName, "ep.time"}, + {ColumnName, "x.Age"}, + {ColumnName, "x.Name"}, {Keyword, "ALL"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(6, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } } @@ -1418,9 +1436,9 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { }; TVector<TCandidate> expected = { - {BindingName, "$x"}, {ColumnName, "Age"}, {ColumnName, "Name"}, + {BindingName, "$x"}, }; TEnvironment env = { @@ -1536,11 +1554,12 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { )sql"; TVector<TCandidate> expected = { + {ColumnName, "room"}, + {ColumnName, "query.room"}, {ColumnName, "roommate.Age"}, {ColumnName, "roommate.Name"}, - {ColumnName, "query.room"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(4, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); } Y_UNIT_TEST(QualifiedColumnAtWhere) { @@ -1564,13 +1583,15 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { TString prefix = R"sql(SELECT * FROM example.`/people` AS x )sql"; TVector<TCandidate> expected = { + {ColumnName, "Age"}, + {ColumnName, "Name"}, {ColumnName, "x.Age"}, {ColumnName, "x.Name"}, }; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, prefix + "WHERE #"), expected); - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, prefix + "GROUP BY #"), expected); - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, prefix + "HAVING #"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, prefix + "WHERE #"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, prefix + "GROUP BY #"), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, prefix + "HAVING #"), expected); } Y_UNIT_TEST(ColumnFromQuotedAlias) { @@ -1579,14 +1600,23 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { TString query; TVector<TCandidate> expected = { + {ColumnName, "Age"}, + {ColumnName, "Name"}, {ColumnName, "`per son`.Age"}, {ColumnName, "`per son`.Name"}, }; query = R"sql(SELECT # FROM example.`/people` AS `per son`)sql"; - UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, query), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); + } + { + TString query = R"sql(SELECT per# FROM example.`/people` AS `per son`)sql"; + + TVector<TCandidate> expected = { + {ColumnName, "`per son`.Age"}, + {ColumnName, "`per son`.Name"}, + }; - query = R"sql(SELECT per# FROM example.`/people` AS `per son`)sql"; UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, query), expected); } { @@ -1640,11 +1670,10 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { prefix + R"sql(SELECT * FROM $source AS x FROM x.#)sql", }; - // FIXME: change ranking. TVector<TCandidate> expected = { - {BindingName, "$source"}, {ColumnName, "Age"}, {ColumnName, "Name"}, + {BindingName, "$source"}, }; UNIT_ASSERT_VALUES_EQUAL(CompleteTop(3, engine, input[0]), expected); @@ -1667,6 +1696,82 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { UNIT_ASSERT_VALUES_EQUAL(Complete(engine, "SELECT `Y #` FROM saurus.maxim").size(), 0); } + Y_UNIT_TEST(ColumnReplicationConflict) { + auto engine = MakeSqlCompletionEngineUT(); + + TString query = R"sql( + SELECT # + FROM (SELECT a, b) AS x + JOIN (SELECT a, b) AS y + )sql"; + + TVector<TCandidate> expected = { + {ColumnName, "x.a"}, + {ColumnName, "x.b"}, + {ColumnName, "y.a"}, + {ColumnName, "y.b"}, + }; + + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); + } + + Y_UNIT_TEST(ColumnReplication) { + auto engine = MakeSqlCompletionEngineUT(); + + TString query = R"sql( + SELECT # + FROM (SELECT a, b) AS x + JOIN (SELECT b, c) AS y + )sql"; + + TVector<TCandidate> expected = { + {ColumnName, "a"}, + {ColumnName, "c"}, + {ColumnName, "x.a"}, + {ColumnName, "x.b"}, + {ColumnName, "y.b"}, + {ColumnName, "y.c"}, + }; + + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); + } + + Y_UNIT_TEST(ColumnReplicationCaseSensivity) { + auto engine = MakeSqlCompletionEngineUT(); + + TString query = R"sql( + SELECT # + FROM (SELECT A, B) AS x + JOIN (SELECT a, b) AS y + )sql"; + + TVector<TCandidate> expected = { + {ColumnName, "A"}, + {ColumnName, "B"}, + {ColumnName, "a"}, + {ColumnName, "b"}, + }; + + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, query), expected); + } + + Y_UNIT_TEST(ColumnReplicationFiltration) { + auto engine = MakeSqlCompletionEngineUT(); + + TVector<TString> queries = { + R"sql(SELECT x# FROM (SELECT XXX) AS xxx)sql", + R"sql(SELECT X# FROM (SELECT XXX) AS xxx)sql", + }; + + TVector<TCandidate> expected = { + {ColumnName, "XXX"}, + {ColumnName, "xxx.XXX"}, + }; + + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, queries[0]), expected); + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(expected.size(), engine, queries[1]), expected); + } + Y_UNIT_TEST(NoBindingAtQuoted) { auto engine = MakeSqlCompletionEngineUT(); |
