diff options
author | vityaman <[email protected]> | 2025-06-02 13:23:33 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-06-02 13:49:48 +0300 |
commit | f8dd75681a31f8b883f1c69db243a1ded8bb8d94 (patch) | |
tree | 4175cf2925fdc77e7f45bb9f84b65cc06a1a6829 /yql/essentials/sql/v1/complete | |
parent | 70ff3797ac5c7fd31a1f092b4074dee5668fa0b1 (diff) |
YQL-19747: Complete named expressions
- Introduced scoped name collection at global analysis
- Added `BingingsNameService` constructed after global analysis and united with the base service.
- Added dollar detection at local analysis.
After this point design should be reexamined and module should be refactored.
---
- Related to `YQL-19747`
- Related to https://github.com/ydb-platform/ydb/issues/9056
- Related to https://github.com/vityaman/ydb/issues/57
---
Pull Request resolved: https://github.com/ytsaurus/ytsaurus/pull/1304
commit_hash:a7bf41703f0ee846e359407b53a5d62fc05928ea
Diffstat (limited to 'yql/essentials/sql/v1/complete')
33 files changed, 555 insertions, 69 deletions
diff --git a/yql/essentials/sql/v1/complete/analysis/global/evaluate.cpp b/yql/essentials/sql/v1/complete/analysis/global/evaluate.cpp index b938eaf0631..f012c77583c 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/evaluate.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/evaluate.cpp @@ -12,10 +12,15 @@ namespace NSQLComplete { } std::any visitBind_parameter(SQLv1::Bind_parameterContext* ctx) override { - std::string id = GetId(ctx); - if (const NYT::TNode* node = Env_->Parameters.FindPtr(id)) { + TMaybe<std::string> id = GetId(ctx); + if (id.Empty()) { + return defaultResult(); + } + + if (const NYT::TNode* node = Env_->Parameters.FindPtr(*id)) { return *node; } + return defaultResult(); } @@ -24,18 +29,6 @@ namespace NSQLComplete { } private: - std::string GetId(SQLv1::Bind_parameterContext* ctx) const { - if (auto* x = ctx->an_id_or_type()) { - return x->getText(); - } else if (auto* x = ctx->TOKEN_TRUE()) { - return x->getText(); - } else if (auto* x = ctx->TOKEN_FALSE()) { - return x->getText(); - } else { - Y_ABORT("You should change implementation according grammar changes"); - } - } - const TEnvironment* Env_; }; diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.cpp b/yql/essentials/sql/v1/complete/analysis/global/global.cpp index 9acb3310fac..c5f33ae8297 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/global.cpp @@ -1,5 +1,6 @@ #include "global.h" +#include "named_node.h" #include "parse_tree.h" #include "use.h" @@ -48,7 +49,9 @@ namespace NSQLComplete { TGlobalContext ctx; + // TODO(YQL-19747): Add ~ParseContext(Tokens, ParseTree, CursorPosition) ctx.Use = FindUseStatement(sqlQuery, &Tokens_, input.CursorPosition, env); + ctx.Names = CollectNamedNodes(sqlQuery, &Tokens_, input.CursorPosition); return ctx; } diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.h b/yql/essentials/sql/v1/complete/analysis/global/global.h index 97c55738e45..2c30dbf1cf5 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global.h +++ b/yql/essentials/sql/v1/complete/analysis/global/global.h @@ -6,6 +6,7 @@ #include <util/generic/ptr.h> #include <util/generic/maybe.h> #include <util/generic/string.h> +#include <util/generic/vector.h> namespace NSQLComplete { @@ -16,6 +17,7 @@ namespace NSQLComplete { struct TGlobalContext { TMaybe<TUseContext> Use; + TVector<TString> Names; }; class IGlobalAnalysis { diff --git a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp new file mode 100644 index 00000000000..643af6efc9c --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp @@ -0,0 +1,72 @@ +#include "global.h" + +#include <library/cpp/testing/unittest/registar.h> + +using namespace NSQLComplete; + +Y_UNIT_TEST_SUITE(GlobalAnalysisTests) { + + Y_UNIT_TEST(TopLevelNamesCollected) { + IGlobalAnalysis::TPtr global = MakeGlobalAnalysis(); + + TString query = R"( + DECLARE $cluster_name AS String; + + IMPORT math SYMBOLS $sqrt, $pow; + + $sqrt = 0; + + DEFINE ACTION $hello_world($name, $suffix?) AS + $name = $name ?? ($suffix ?? "world"); + SELECT "Hello, " || $name || "!"; + END DEFINE; + + $first, $second, $_ = AsTuple(1, 2, 3); + )"; + + TGlobalContext ctx = global->Analyze({query}, {}); + Sort(ctx.Names); + + TVector<TString> expected = { + "cluster_name", + "first", + "hello_world", + "pow", + "second", + "sqrt", + }; + UNIT_ASSERT_VALUES_EQUAL(ctx.Names, expected); + } + + Y_UNIT_TEST(LocalNamesCollected) { + IGlobalAnalysis::TPtr global = MakeGlobalAnalysis(); + + TString query = R"( + DEFINE ACTION $sum($x, $y) AS + $acc = 0; + EVALUATE FOR $i IN AsList($x, $y) DO BEGIN + $plus = ($a, $b) -> (#); + $acc = $plus($acc, $i); + END DO; + END DEFINE; + )"; + + TCompletionInput input = SharpedInput(query); + + TGlobalContext ctx = global->Analyze(input, {}); + Sort(ctx.Names); + + TVector<TString> expected = { + "a", + "acc", + "b", + "i", + "plus", + "sum", + "x", + "y", + }; + UNIT_ASSERT_VALUES_EQUAL(ctx.Names, expected); + } + +} // Y_UNIT_TEST_SUITE(GlobalAnalysisTests) diff --git a/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp b/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp new file mode 100644 index 00000000000..14b928c53e5 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/named_node.cpp @@ -0,0 +1,118 @@ +#include "named_node.h" + +#include "narrowing_visitor.h" + +#include <library/cpp/iterator/iterate_keys.h> +#include <library/cpp/iterator/iterate_values.h> + +#include <util/generic/hash_set.h> +#include <util/generic/ptr.h> + +namespace NSQLComplete { + + namespace { + + class TVisitor: public TSQLv1NarrowingVisitor { + public: + TVisitor( + antlr4::TokenStream* tokens, + size_t cursorPosition, + THashSet<TString>* names) + : TSQLv1NarrowingVisitor(tokens, cursorPosition) + , Names_(names) + { + } + + std::any visitSql_stmt_core(SQLv1::Sql_stmt_coreContext* ctx) override { + if (ctx->declare_stmt() || + ctx->import_stmt() || + ctx->define_action_or_subquery_stmt() || + ctx->named_nodes_stmt() || + IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitDeclare_stmt(SQLv1::Declare_stmtContext* ctx) override { + VisitNullable(ctx->bind_parameter()); + return {}; + } + + std::any visitImport_stmt(SQLv1::Import_stmtContext* ctx) override { + VisitNullable(ctx->named_bind_parameter_list()); + return {}; + } + + std::any visitDefine_action_or_subquery_stmt( + SQLv1::Define_action_or_subquery_stmtContext* ctx) override { + VisitNullable(ctx->bind_parameter()); + if (IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitNamed_nodes_stmt(SQLv1::Named_nodes_stmtContext* ctx) override { + VisitNullable(ctx->bind_parameter_list()); + if (IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitFor_stmt(SQLv1::For_stmtContext* ctx) override { + VisitNullable(ctx->bind_parameter()); + if (IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitLambda(SQLv1::LambdaContext* ctx) override { + VisitNullable(ctx->smart_parenthesis()); + if (IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitNamed_bind_parameter( + SQLv1::Named_bind_parameterContext* ctx) override { + VisitNullable(ctx->bind_parameter(0)); + return {}; + } + + std::any visitBind_parameter(SQLv1::Bind_parameterContext* ctx) override { + TMaybe<std::string> id = GetId(ctx); + if (id.Empty() || id == "_") { + return {}; + } + + Names_->emplace(std::move(*id)); + return {}; + } + + private: + void VisitNullable(antlr4::tree::ParseTree* tree) { + if (tree == nullptr) { + return; + } + visit(tree); + } + + THashSet<TString>* Names_; + }; + + } // namespace + + TVector<TString> CollectNamedNodes( + SQLv1::Sql_queryContext* ctx, + antlr4::TokenStream* tokens, + size_t cursorPosition) { + THashSet<TString> names; + TVisitor(tokens, cursorPosition, &names).visit(ctx); + return TVector<TString>(begin(names), end(names)); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/named_node.h b/yql/essentials/sql/v1/complete/analysis/global/named_node.h new file mode 100644 index 00000000000..119fbc4d5f6 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/named_node.h @@ -0,0 +1,15 @@ +#pragma once + +#include "parse_tree.h" + +#include <util/generic/string.h> +#include <util/generic/vector.h> + +namespace NSQLComplete { + + TVector<TString> CollectNamedNodes( + SQLv1::Sql_queryContext* ctx, + antlr4::TokenStream* tokens, + size_t cursorPosition); + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp new file mode 100644 index 00000000000..eefb6e70f5e --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.cpp @@ -0,0 +1,33 @@ +#include "narrowing_visitor.h" + +namespace NSQLComplete { + + TSQLv1NarrowingVisitor::TSQLv1NarrowingVisitor(antlr4::TokenStream* tokens, size_t cursorPosition) + : Tokens_(tokens) + , CursorPosition_(cursorPosition) + { + } + + bool TSQLv1NarrowingVisitor::shouldVisitNextChild(antlr4::tree::ParseTree* node, const std::any& /*currentResult*/) { + return TextInterval(node).a < static_cast<ssize_t>(CursorPosition_); + } + + bool TSQLv1NarrowingVisitor::IsEnclosing(antlr4::tree::ParseTree* tree) const { + return TextInterval(tree).properlyContains(CursorInterval()); + } + + antlr4::misc::Interval TSQLv1NarrowingVisitor::TextInterval(antlr4::tree::ParseTree* tree) const { + auto tokens = tree->getSourceInterval(); + if (tokens.b == -1) { + tokens.b = tokens.a; + } + return antlr4::misc::Interval( + Tokens_->get(tokens.a)->getStartIndex(), + Tokens_->get(tokens.b)->getStopIndex()); + } + + antlr4::misc::Interval TSQLv1NarrowingVisitor::CursorInterval() const { + return antlr4::misc::Interval(CursorPosition_, CursorPosition_); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h new file mode 100644 index 00000000000..d5375c20322 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/narrowing_visitor.h @@ -0,0 +1,23 @@ +#pragma once + +#include "parse_tree.h" + +namespace NSQLComplete { + + class TSQLv1NarrowingVisitor: public SQLv1Antlr4BaseVisitor { + public: + TSQLv1NarrowingVisitor(antlr4::TokenStream* tokens, size_t cursorPosition); + + protected: + bool IsEnclosing(antlr4::tree::ParseTree* tree) const; + bool shouldVisitNextChild(antlr4::tree::ParseTree* node, const std::any& /*currentResult*/) override; + + private: + antlr4::misc::Interval TextInterval(antlr4::tree::ParseTree* tree) const; + antlr4::misc::Interval CursorInterval() const; + + antlr4::TokenStream* Tokens_; + size_t CursorPosition_; + }; + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/parse_tree.cpp b/yql/essentials/sql/v1/complete/analysis/global/parse_tree.cpp new file mode 100644 index 00000000000..2ed82752a01 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/parse_tree.cpp @@ -0,0 +1,20 @@ +#include "parse_tree.h" + +#include <util/system/yassert.h> +#include <util/generic/maybe.h> + +namespace NSQLComplete { + + TMaybe<std::string> GetId(SQLv1::Bind_parameterContext* ctx) { + if (auto* x = ctx->an_id_or_type()) { + return x->getText(); + } else if (auto* x = ctx->TOKEN_TRUE()) { + return x->getText(); + } else if (auto* x = ctx->TOKEN_FALSE()) { + return x->getText(); + } else { + return Nothing(); + } + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/parse_tree.h b/yql/essentials/sql/v1/complete/analysis/global/parse_tree.h index 25ca6d336cb..f194232aea5 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/parse_tree.h +++ b/yql/essentials/sql/v1/complete/analysis/global/parse_tree.h @@ -1,5 +1,7 @@ #pragma once +#include <util/generic/maybe.h> + #ifdef TOKEN_QUERY // Conflict with the winnt.h #undef TOKEN_QUERY #endif @@ -15,4 +17,6 @@ namespace NSQLComplete { using NALADefaultAntlr4::SQLv1Antlr4BaseVisitor; + TMaybe<std::string> GetId(SQLv1::Bind_parameterContext* ctx); + } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/use.cpp b/yql/essentials/sql/v1/complete/analysis/global/use.cpp index 2fd7fab6614..717a35e4e8f 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/use.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/use.cpp @@ -1,19 +1,19 @@ #include "use.h" #include "evaluate.h" +#include "narrowing_visitor.h" namespace NSQLComplete { namespace { - class TVisitor: public SQLv1Antlr4BaseVisitor { + class TVisitor: public TSQLv1NarrowingVisitor { public: TVisitor( antlr4::TokenStream* tokens, size_t cursorPosition, const TEnvironment* env) - : Tokens_(tokens) - , CursorPosition_(cursorPosition) + : TSQLv1NarrowingVisitor(tokens, cursorPosition) , Env_(env) { } @@ -61,29 +61,7 @@ namespace NSQLComplete { return aggregate; } - bool shouldVisitNextChild(antlr4::tree::ParseTree* node, const std::any& /*currentResult*/) override { - return TextInterval(node).a < static_cast<ssize_t>(CursorPosition_); - } - private: - bool IsEnclosing(antlr4::tree::ParseTree* tree) const { - return TextInterval(tree).properlyContains(CursorInterval()); - } - - antlr4::misc::Interval TextInterval(antlr4::tree::ParseTree* tree) const { - auto tokens = tree->getSourceInterval(); - if (tokens.b == -1) { - tokens.b = tokens.a; - } - return antlr4::misc::Interval( - Tokens_->get(tokens.a)->getStartIndex(), - Tokens_->get(tokens.b)->getStopIndex()); - } - - antlr4::misc::Interval CursorInterval() const { - return antlr4::misc::Interval(CursorPosition_, CursorPosition_); - } - TMaybe<TString> GetId(SQLv1::Pure_column_or_namedContext* ctx) const { if (auto* x = ctx->bind_parameter()) { return GetId(x); @@ -102,8 +80,6 @@ namespace NSQLComplete { return node.AsString(); } - antlr4::TokenStream* Tokens_; - size_t CursorPosition_; const TEnvironment* Env_; }; diff --git a/yql/essentials/sql/v1/complete/analysis/global/ut/ya.make b/yql/essentials/sql/v1/complete/analysis/global/ut/ya.make new file mode 100644 index 00000000000..daeeee04b73 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/ut/ya.make @@ -0,0 +1,7 @@ +UNITTEST_FOR(yql/essentials/sql/v1/complete/analysis/global) + +SRCS( + global_ut.cpp +) + +END() diff --git a/yql/essentials/sql/v1/complete/analysis/global/ya.make b/yql/essentials/sql/v1/complete/analysis/global/ya.make index a8aa1eb7214..da6f4a17381 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/ya.make +++ b/yql/essentials/sql/v1/complete/analysis/global/ya.make @@ -3,13 +3,21 @@ LIBRARY() SRCS( evaluate.cpp global.cpp + named_node.cpp + narrowing_visitor.cpp + parse_tree.cpp use.cpp ) PEERDIR( yql/essentials/sql/v1/complete/core + yql/essentials/sql/v1/complete/syntax yql/essentials/parser/antlr_ast/gen/v1_antlr4 yql/essentials/parser/antlr_ast/gen/v1_ansi_antlr4 ) END() + +RECURSE_FOR_TESTS( + ut +) diff --git a/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp b/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp new file mode 100644 index 00000000000..9de6380410b --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/binding/name_service.cpp @@ -0,0 +1,56 @@ +#include "name_service.h" + +#include <yql/essentials/sql/v1/complete/name/service/static/name_index.h> + +namespace NSQLComplete { + + namespace { + + class TNameService: public INameService { + public: + explicit TNameService(TVector<TString> names) + : Index_(BuildNameIndex(std::move(names), NormalizeName)) + { + } + + NThreading::TFuture<TNameResponse> Lookup(TNameRequest request) const override { + if (request.Constraints.IsEmpty()) { + return NThreading::MakeFuture<TNameResponse>({}); + } + + TVector<TStringBuf> filtered = + FilteredByPrefix(request.Prefix, Index_, NormalizeName); + filtered.crop(request.Limit); + + return NThreading::MakeFuture<TNameResponse>({ + .RankedNames = Transform(filtered), + }); + } + + private: + static TVector<TGenericName> Transform(TVector<TStringBuf> names) { + TVector<TGenericName> generic; + generic.reserve(names.size()); + for (TStringBuf name : names) { + generic.emplace_back(Transform(name)); + } + return generic; + } + + static TGenericName Transform(TStringBuf name) { + TBindingName unknown; + unknown.Indentifier = name; + return unknown; + } + + TNameIndex Index_; + IRanking::TPtr Ranking_; + }; + + } // namespace + + INameService::TPtr MakeBindingNameService(TVector<TString> names) { + return new TNameService(std::move(names)); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/binding/name_service.h b/yql/essentials/sql/v1/complete/name/service/binding/name_service.h new file mode 100644 index 00000000000..8fdac1a8d7e --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/binding/name_service.h @@ -0,0 +1,12 @@ +#pragma once + +#pragma once + +#include <yql/essentials/sql/v1/complete/name/service/name_service.h> +#include <yql/essentials/sql/v1/complete/name/service/ranking/ranking.h> + +namespace NSQLComplete { + + INameService::TPtr MakeBindingNameService(TVector<TString> names); + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/binding/ya.make b/yql/essentials/sql/v1/complete/name/service/binding/ya.make new file mode 100644 index 00000000000..54ba24fcff4 --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/binding/ya.make @@ -0,0 +1,12 @@ +LIBRARY() + +SRCS( + name_service.cpp +) + +PEERDIR( + yql/essentials/sql/v1/complete/name/service + yql/essentials/sql/v1/complete/name/service/static +) + +END() 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 cdcfc3a4982..bff94f9504c 100644 --- a/yql/essentials/sql/v1/complete/name/service/name_service.h +++ b/yql/essentials/sql/v1/complete/name/service/name_service.h @@ -58,6 +58,9 @@ namespace NSQLComplete { struct TConstraints: TNamespaced {}; }; + struct TBindingName: TIndentifier { + }; + struct TUnkownName { TString Content; TString Type; @@ -72,6 +75,7 @@ namespace NSQLComplete { TFolderName, TTableName, TClusterName, + TBindingName, TUnkownName>; struct TNameConstraints { @@ -82,6 +86,15 @@ namespace NSQLComplete { TMaybe<TObjectNameConstraints> Object; TMaybe<TClusterName::TConstraints> Cluster; + bool IsEmpty() const { + return !Pragma && + !Type && + !Function && + !Hint && + !Object && + !Cluster; + } + TGenericName Qualified(TGenericName unqualified) const; TGenericName Unqualified(TGenericName qualified) const; TVector<TGenericName> Qualified(TVector<TGenericName> unqualified) const; @@ -95,19 +108,13 @@ namespace NSQLComplete { size_t Limit = 128; bool IsEmpty() const { - return Keywords.empty() && - !Constraints.Pragma && - !Constraints.Type && - !Constraints.Function && - !Constraints.Hint && - !Constraints.Object && - !Constraints.Cluster; + return Keywords.empty() && Constraints.IsEmpty(); } }; struct TNameResponse { TVector<TGenericName> RankedNames; - TMaybe<size_t> NameHintLength; + TMaybe<size_t> NameHintLength = Nothing(); bool IsEmpty() const { return RankedNames.empty(); diff --git a/yql/essentials/sql/v1/complete/name/service/ranking/dummy.cpp b/yql/essentials/sql/v1/complete/name/service/ranking/dummy.cpp new file mode 100644 index 00000000000..c8dc320cc73 --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/ranking/dummy.cpp @@ -0,0 +1,23 @@ +#include "dummy.h" + +namespace NSQLComplete { + + namespace { + + class TRanking: public IRanking { + public: + void CropToSortedPrefix( + TVector<TGenericName>& names, + const TNameConstraints& /* constraints */, + size_t limit) const override { + names.crop(limit); + } + }; + + } // namespace + + IRanking::TPtr MakeDummyRanking() { + return new TRanking(); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/ranking/dummy.h b/yql/essentials/sql/v1/complete/name/service/ranking/dummy.h new file mode 100644 index 00000000000..f9f024fe7ab --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/ranking/dummy.h @@ -0,0 +1,9 @@ +#pragma once + +#include "ranking.h" + +namespace NSQLComplete { + + IRanking::TPtr MakeDummyRanking(); + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/service/ranking/ya.make b/yql/essentials/sql/v1/complete/name/service/ranking/ya.make index 56e8e782128..e5464cf1f96 100644 --- a/yql/essentials/sql/v1/complete/name/service/ranking/ya.make +++ b/yql/essentials/sql/v1/complete/name/service/ranking/ya.make @@ -1,6 +1,7 @@ LIBRARY() SRCS( + dummy.cpp frequency.cpp ranking.cpp ) diff --git a/yql/essentials/sql/v1/complete/name/service/static/name_index.cpp b/yql/essentials/sql/v1/complete/name/service/static/name_index.cpp new file mode 100644 index 00000000000..707c367385f --- /dev/null +++ b/yql/essentials/sql/v1/complete/name/service/static/name_index.cpp @@ -0,0 +1 @@ +#include "name_index.h" diff --git a/yql/essentials/sql/v1/complete/name/service/static/name_index.h b/yql/essentials/sql/v1/complete/name/service/static/name_index.h index 840a4a5fe44..271d7beca48 100644 --- a/yql/essentials/sql/v1/complete/name/service/static/name_index.h +++ b/yql/essentials/sql/v1/complete/name/service/static/name_index.h @@ -39,4 +39,24 @@ namespace NSQLComplete { return index; } + const TVector<TStringBuf> FilteredByPrefix( + const TString& prefix, + const TNameIndex& index Y_LIFETIME_BOUND, + auto normalize) { + TNameIndexEntry normalized = { + .Normalized = normalize(prefix), + .Original = "", + }; + + auto range = std::ranges::equal_range( + std::begin(index), std::end(index), + normalized, NameIndexCompareLimit(normalized.Normalized.size())); + + TVector<TStringBuf> filtered; + for (const TNameIndexEntry& entry : range) { + filtered.emplace_back(TStringBuf(entry.Original)); + } + return filtered; + } + } // namespace NSQLComplete 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 976646dd7f4..fd6aad714ee 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 @@ -8,23 +8,6 @@ namespace NSQLComplete { - const TVector<TStringBuf> FilteredByPrefix(const TString& prefix, const TNameIndex& index Y_LIFETIME_BOUND) { - TNameIndexEntry normalized = { - .Normalized = NormalizeName(prefix), - .Original = "", - }; - - auto range = std::ranges::equal_range( - std::begin(index), std::end(index), - normalized, NameIndexCompareLimit(normalized.Normalized.size())); - - TVector<TStringBuf> filtered; - for (const TNameIndexEntry& entry : range) { - filtered.emplace_back(TStringBuf(entry.Original)); - } - return filtered; - } - const TVector<TStringBuf> FilteredByPrefix( const TString& prefix, const TVector<TString>& sorted Y_LIFETIME_BOUND) { @@ -51,7 +34,7 @@ namespace NSQLComplete { name.Indentifier = prefix; name = std::get<T>(constraints.Qualified(std::move(name))); - AppendAs<T>(out, FilteredByPrefix(name.Indentifier, index)); + AppendAs<T>(out, FilteredByPrefix(name.Indentifier, index, NormalizeName)); out = constraints.Unqualified(std::move(out)); } diff --git a/yql/essentials/sql/v1/complete/name/service/static/ya.make b/yql/essentials/sql/v1/complete/name/service/static/ya.make index 317ef4ed84a..e1211e223e0 100644 --- a/yql/essentials/sql/v1/complete/name/service/static/ya.make +++ b/yql/essentials/sql/v1/complete/name/service/static/ya.make @@ -1,6 +1,7 @@ LIBRARY() SRCS( + name_index.cpp name_service.cpp name_set_json.cpp name_set.cpp diff --git a/yql/essentials/sql/v1/complete/name/service/ya.make b/yql/essentials/sql/v1/complete/name/service/ya.make index ec4de4d5e10..2f8118f882d 100644 --- a/yql/essentials/sql/v1/complete/name/service/ya.make +++ b/yql/essentials/sql/v1/complete/name/service/ya.make @@ -12,6 +12,7 @@ PEERDIR( END() RECURSE( + binding cluster ranking schema diff --git a/yql/essentials/sql/v1/complete/sql_complete.cpp b/yql/essentials/sql/v1/complete/sql_complete.cpp index 807786512e4..eb7610640a1 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete.cpp @@ -2,7 +2,10 @@ #include <yql/essentials/sql/v1/complete/syntax/grammar.h> #include <yql/essentials/sql/v1/complete/text/word.h> +#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/static/name_service.h> +#include <yql/essentials/sql/v1/complete/name/service/union/name_service.h> #include <yql/essentials/sql/v1/complete/syntax/local.h> #include <yql/essentials/sql/v1/complete/syntax/format.h> #include <yql/essentials/sql/v1/complete/analysis/global/global.h> @@ -52,7 +55,14 @@ namespace NSQLComplete { }); } - return Names_->Lookup(std::move(request)) + TVector<INameService::TPtr> children; + children.emplace_back(MakeBindingNameService(std::move(global.Names))); + if (!context.Binding) { + children.emplace_back(Names_); + } + + return MakeUnionNameService(std::move(children), MakeDummyRanking()) + ->Lookup(std::move(request)) .Apply([this, input, context = std::move(context)](auto f) { return ToCompletion(input, context, f.ExtractValue()); }); @@ -202,6 +212,13 @@ namespace NSQLComplete { return {ECandidateKind::ClusterName, std::move(name.Indentifier)}; } + if constexpr (std::is_base_of_v<TBindingName, T>) { + if (!context.Binding) { + name.Indentifier.prepend('$'); + } + return {ECandidateKind::BindingName, std::move(name.Indentifier)}; + } + if constexpr (std::is_base_of_v<TUnkownName, T>) { return {ECandidateKind::UnknownName, std::move(name.Content)}; } @@ -290,6 +307,9 @@ void Out<NSQLComplete::ECandidateKind>(IOutputStream& out, NSQLComplete::ECandid case NSQLComplete::ECandidateKind::ClusterName: out << "ClusterName"; break; + case NSQLComplete::ECandidateKind::BindingName: + out << "BindingName"; + break; case NSQLComplete::ECandidateKind::UnknownName: out << "UnknownName"; break; diff --git a/yql/essentials/sql/v1/complete/sql_complete.h b/yql/essentials/sql/v1/complete/sql_complete.h index e5dffcb8d27..6174f913254 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.h +++ b/yql/essentials/sql/v1/complete/sql_complete.h @@ -27,6 +27,7 @@ namespace NSQLComplete { FolderName, TableName, ClusterName, + BindingName, UnknownName, }; diff --git a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp index 7460924b48f..cc39fa875cc 100644 --- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp @@ -39,6 +39,7 @@ public: }; Y_UNIT_TEST_SUITE(SqlCompleteTests) { + using ECandidateKind::BindingName; using ECandidateKind::ClusterName; using ECandidateKind::FolderName; using ECandidateKind::FunctionName; @@ -679,6 +680,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } { TVector<TCandidate> expected = { + {BindingName, "$hello"}, {TableName, "`maxim"}, {ClusterName, "example"}, {ClusterName, "saurus"}, @@ -694,6 +696,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { } { TVector<TCandidate> expected = { + {BindingName, "$action"}, {TableName, "`people"}, {FolderName, "`yql/"}, {ClusterName, "example"}, @@ -983,6 +986,44 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { UNIT_ASSERT_GE(Complete(engine, "SELECT 1;").size(), 35); } + Y_UNIT_TEST(Bindings) { + auto engine = MakeSqlCompletionEngineUT(); + + TString query = R"( + $=0; + $a=0; + $abac=0; + SELECT + )"; + + { + TVector<TCandidate> expected = { + {BindingName, "$a"}, + {BindingName, "$abac"}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, query), expected); + } + { + TVector<TCandidate> expected = { + {BindingName, "$abac"}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(1, engine, query + "ab"), expected); + } + { + TVector<TCandidate> expected = { + {BindingName, "a"}, + {BindingName, "abac"}, + }; + UNIT_ASSERT_VALUES_EQUAL(Complete(engine, query + "$"), expected); + } + { + TVector<TCandidate> expected = { + {BindingName, "abac"}, + }; + UNIT_ASSERT_VALUES_EQUAL(Complete(engine, query + "$ab"), expected); + } + } + Y_UNIT_TEST(Typing) { const auto queryUtf16 = TUtf16String::FromUtf8( "SELECT \n" diff --git a/yql/essentials/sql/v1/complete/syntax/local.cpp b/yql/essentials/sql/v1/complete/syntax/local.cpp index 772f5b78dd5..8e956dc66db 100644 --- a/yql/essentials/sql/v1/complete/syntax/local.cpp +++ b/yql/essentials/sql/v1/complete/syntax/local.cpp @@ -101,6 +101,7 @@ namespace NSQLComplete { result.Hint = HintMatch(candidates); result.Object = ObjectMatch(context, candidates); result.Cluster = ClusterMatch(context, candidates); + result.Binding = BindingMatch(candidates); return result; } @@ -292,6 +293,10 @@ namespace NSQLComplete { return cluster; } + bool BindingMatch(const TC3Candidates& candidates) const { + return AnyOf(candidates.Rules, RuleAdapted(IsLikelyBindingStack)); + } + TEditRange EditRange(const TCursorTokenContext& context) const { if (auto enclosing = context.Enclosing()) { return EditRange(*enclosing, context.Cursor); diff --git a/yql/essentials/sql/v1/complete/syntax/local.h b/yql/essentials/sql/v1/complete/syntax/local.h index 0017afa0684..da44969c746 100644 --- a/yql/essentials/sql/v1/complete/syntax/local.h +++ b/yql/essentials/sql/v1/complete/syntax/local.h @@ -55,6 +55,7 @@ namespace NSQLComplete { TMaybe<THint> Hint; TMaybe<TObject> Object; TMaybe<TCluster> Cluster; + bool Binding = false; TEditRange EditRange; }; diff --git a/yql/essentials/sql/v1/complete/syntax/parser_call_stack.cpp b/yql/essentials/sql/v1/complete/syntax/parser_call_stack.cpp index 03e07181a0d..efde1ea16b6 100644 --- a/yql/essentials/sql/v1/complete/syntax/parser_call_stack.cpp +++ b/yql/essentials/sql/v1/complete/syntax/parser_call_stack.cpp @@ -73,7 +73,11 @@ namespace NSQLComplete { RULE(Named_expr_list), RULE(Named_expr), RULE(Expr)}, stack) && - EndsWith({RULE(Atom_expr), RULE(An_id_or_type)}, stack)); + (EndsWith({RULE(Atom_expr), + RULE(An_id_or_type)}, stack) || + EndsWith({RULE(Atom_expr), + RULE(Bind_parameter), + RULE(An_id_or_type)}, stack))); } bool IsLikelyFunctionStack(const TParserCallStack& stack) { @@ -81,6 +85,10 @@ namespace NSQLComplete { EndsWith({RULE(Unary_casual_subexpr), RULE(Atom_expr), RULE(An_id_or_type)}, stack) || + EndsWith({RULE(Unary_casual_subexpr), + RULE(Atom_expr), + RULE(Bind_parameter), + RULE(An_id_or_type)}, stack) || EndsWith({RULE(Atom_expr), RULE(Id_or_type)}, stack); } @@ -109,6 +117,10 @@ namespace NSQLComplete { return Contains({RULE(Cluster_expr)}, stack); } + bool IsLikelyBindingStack(const TParserCallStack& stack) { + return EndsWith({RULE(Bind_parameter), RULE(An_id_or_type)}, stack); + } + TMaybe<EStatementKind> StatementKindOf(const TParserCallStack& stack) { for (TRuleId rule : std::ranges::views::reverse(stack)) { if (rule == RULE(Process_core) || rule == RULE(Reduce_core) || rule == RULE(Select_core)) { diff --git a/yql/essentials/sql/v1/complete/syntax/parser_call_stack.h b/yql/essentials/sql/v1/complete/syntax/parser_call_stack.h index d44b824a05e..c8daf9114fe 100644 --- a/yql/essentials/sql/v1/complete/syntax/parser_call_stack.h +++ b/yql/essentials/sql/v1/complete/syntax/parser_call_stack.h @@ -21,6 +21,8 @@ namespace NSQLComplete { bool IsLikelyClusterStack(const TParserCallStack& stack); + bool IsLikelyBindingStack(const TParserCallStack& stack); + TMaybe<EStatementKind> StatementKindOf(const TParserCallStack& stack); std::unordered_set<TRuleId> GetC3PreferredRules(); diff --git a/yql/essentials/sql/v1/complete/ya.make b/yql/essentials/sql/v1/complete/ya.make index e2a7d7db5f9..aeec31d506a 100644 --- a/yql/essentials/sql/v1/complete/ya.make +++ b/yql/essentials/sql/v1/complete/ya.make @@ -12,6 +12,10 @@ PEERDIR( yql/essentials/sql/v1/complete/syntax yql/essentials/sql/v1/complete/analysis/global yql/essentials/sql/v1/complete/text + # TODO(YQL-19747): split /name/service/ranking interface and implementation + # TODO(YQL-19747): extract NameIndex + yql/essentials/sql/v1/complete/name/service/ranking + yql/essentials/sql/v1/complete/name/service/binding # TODO(YQL-19747): add it to YDB CLI PEERDIR yql/essentials/sql/v1/complete/name/service/static ) |