diff options
author | vitya-smirnov <[email protected]> | 2025-06-17 12:45:20 +0300 |
---|---|---|
committer | vitya-smirnov <[email protected]> | 2025-06-17 13:12:27 +0300 |
commit | 633ab50dd51d6f17810f71559ccf6e5bfebe2044 (patch) | |
tree | ebb85b4dff775656e3b5ee1937c1ced01db0f853 | |
parent | bacec6cbd87d14bf55c256f17797537ae8c2bbed (diff) |
YQL-19747: Complete columns at simple select
Added support for a columns completion at a simple select. For example, `` SELECT # FROM hahn.`home/yql/tutorial/users` `` и `` USE hahn; SELECT $ FROM `home/yql/tutorial/users` ``.
commit_hash:2254449e91255c19792a1dc521825e44dda7d63b
26 files changed, 421 insertions, 30 deletions
diff --git a/yql/essentials/sql/v1/complete/analysis/global/column.cpp b/yql/essentials/sql/v1/complete/analysis/global/column.cpp new file mode 100644 index 00000000000..7e185697d3f --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/column.cpp @@ -0,0 +1,116 @@ +#include "column.h" + +#include "narrowing_visitor.h" + +#include <yql/essentials/sql/v1/complete/syntax/format.h> + +namespace NSQLComplete { + + namespace { + + // TODO: Extract it to `identifier.cpp` and reuse it also at `use.cpp` + // and replace `GetId` at `parse_tree.cpp`. + class TIdentifierVisitor: public SQLv1Antlr4BaseVisitor { + public: + std::any visitCluster_expr(SQLv1::Cluster_exprContext* ctx) override { + if (auto* x = ctx->pure_column_or_named()) { + return visit(x); + } + return {}; + } + + std::any visitTable_key(SQLv1::Table_keyContext* ctx) override { + if (auto* x = ctx->id_table_or_type()) { + return visit(x); + } + return {}; + } + + std::any visitTerminal(antlr4::tree::TerminalNode* node) override { + TString text = GetText(node); + switch (node->getSymbol()->getType()) { + case SQLv1::TOKEN_ID_QUOTED: { + text = Unquoted(std::move(text)); + } break; + } + return text; + } + + private: + TString GetText(antlr4::tree::ParseTree* tree) const { + return TString(tree->getText()); + } + }; + + TMaybe<TString> GetId(antlr4::ParserRuleContext* ctx) { + if (ctx == nullptr) { + return Nothing(); + } + + std::any result = TIdentifierVisitor().visit(ctx); + if (!result.has_value()) { + return Nothing(); + } + return std::any_cast<TString>(result); + } + + class TInferenceVisitor: public SQLv1Antlr4BaseVisitor { + public: + std::any visitTable_ref(SQLv1::Table_refContext* ctx) override { + TString cluster = GetId(ctx->cluster_expr()).GetOrElse(""); + + TMaybe<TString> path = GetId(ctx->table_key()); + if (path.Empty()) { + return {}; + } + + return TColumnContext{ + .Tables = { + {.Cluster = std::move(cluster), .Path = std::move(*path)}, + }, + }; + } + }; + + class TVisitor: public TSQLv1NarrowingVisitor { + public: + TVisitor(antlr4::TokenStream* tokens, size_t cursorPosition) + : TSQLv1NarrowingVisitor(tokens, cursorPosition) + { + } + + std::any visitSql_stmt_core(SQLv1::Sql_stmt_coreContext* ctx) override { + if (IsEnclosing(ctx)) { + return visitChildren(ctx); + } + return {}; + } + + std::any visitSelect_core(SQLv1::Select_coreContext* ctx) override { + SQLv1::Join_sourceContext* source = ctx->join_source(0); + if (source == nullptr) { + source = ctx->join_source(1); + } + if (source == nullptr) { + return {}; + } + + return TInferenceVisitor().visit(ctx); + } + }; + + } // namespace + + TMaybe<TColumnContext> InferColumnContext( + SQLv1::Sql_queryContext* ctx, + antlr4::TokenStream* tokens, + size_t cursorPosition) { + // TODO: add utility `auto ToMaybe<T>(std::any any) -> TMaybe<T>` + std::any result = TVisitor(tokens, cursorPosition).visit(ctx); + if (!result.has_value()) { + return Nothing(); + } + return std::any_cast<TColumnContext>(result); + } + +} // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/analysis/global/column.h b/yql/essentials/sql/v1/complete/analysis/global/column.h new file mode 100644 index 00000000000..790dbee1d15 --- /dev/null +++ b/yql/essentials/sql/v1/complete/analysis/global/column.h @@ -0,0 +1,13 @@ +#pragma once + +#include "global.h" +#include "parse_tree.h" + +namespace NSQLComplete { + + TMaybe<TColumnContext> InferColumnContext( + SQLv1::Sql_queryContext* ctx, + antlr4::TokenStream* tokens, + size_t cursorPosition); + +} // 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 8f5df3f6e76..7574b6bdfc0 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 "column.h" #include "function.h" #include "named_node.h" #include "parse_tree.h" @@ -8,6 +9,10 @@ #include <yql/essentials/sql/v1/complete/antlr4/pipeline.h> #include <yql/essentials/sql/v1/complete/syntax/ansi.h> +#include <library/cpp/iterator/functools.h> + +#include <util/string/join.h> + namespace NSQLComplete { class TErrorStrategy: public antlr4::DefaultErrorStrategy { @@ -54,6 +59,11 @@ namespace NSQLComplete { ctx.Use = FindUseStatement(sqlQuery, &Tokens_, input.CursorPosition, env); ctx.Names = CollectNamedNodes(sqlQuery, &Tokens_, input.CursorPosition); ctx.EnclosingFunction = EnclosingFunction(sqlQuery, &Tokens_, input.CursorPosition); + ctx.Column = InferColumnContext(sqlQuery, &Tokens_, input.CursorPosition); + + if (ctx.Use && ctx.Column) { + EnrichTableClusters(*ctx.Column, *ctx.Use); + } return ctx; } @@ -67,6 +77,14 @@ namespace NSQLComplete { return Parser_.sql_query(); } + void EnrichTableClusters(TColumnContext& column, const TUseContext& use) { + for (auto& table : column.Tables) { + if (table.Cluster.empty()) { + table.Cluster = use.Cluster; + } + } + } + antlr4::ANTLRInputStream Chars_; G::TLexer Lexer_; antlr4::CommonTokenStream Tokens_; @@ -97,3 +115,10 @@ namespace NSQLComplete { } } // namespace NSQLComplete + +template <> +void Out<NSQLComplete::TColumnContext>(IOutputStream& out, const NSQLComplete::TColumnContext& value) { + out << "TColumnContext { "; + out << "Tables: " << JoinSeq(", ", value.Tables); + out << " }"; +} diff --git a/yql/essentials/sql/v1/complete/analysis/global/global.h b/yql/essentials/sql/v1/complete/analysis/global/global.h index 3999aeb4a56..fe929bf77c7 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global.h +++ b/yql/essentials/sql/v1/complete/analysis/global/global.h @@ -1,7 +1,8 @@ #pragma once -#include <yql/essentials/sql/v1/complete/core/input.h> #include <yql/essentials/sql/v1/complete/core/environment.h> +#include <yql/essentials/sql/v1/complete/core/input.h> +#include <yql/essentials/sql/v1/complete/core/name.h> #include <util/generic/ptr.h> #include <util/generic/maybe.h> @@ -15,10 +16,17 @@ namespace NSQLComplete { TString Cluster; }; + struct TColumnContext { + TVector<TTableId> Tables; + + friend bool operator==(const TColumnContext& lhs, const TColumnContext& rhs) = default; + }; + struct TGlobalContext { TMaybe<TUseContext> Use; TVector<TString> Names; TMaybe<TString> EnclosingFunction; + TMaybe<TColumnContext> Column; }; 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 index 58aea33b379..c8222580cce 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp +++ b/yql/essentials/sql/v1/complete/analysis/global/global_ut.cpp @@ -98,4 +98,24 @@ Y_UNIT_TEST_SUITE(GlobalAnalysisTests) { } } + Y_UNIT_TEST(SimpleSelectFrom) { + IGlobalAnalysis::TPtr global = MakeGlobalAnalysis(); + { + TString query = "SELECT # FROM plato.Input"; + + TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); + + TColumnContext expected = {.Tables = {{"plato", "Input"}}}; + UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected); + } + { + TString query = "SELECT # FROM plato.`//home/input`"; + + TGlobalContext ctx = global->Analyze(SharpedInput(query), {}); + + TColumnContext expected = {.Tables = {{"plato", "//home/input"}}}; + UNIT_ASSERT_VALUES_EQUAL(ctx.Column, expected); + } + } + } // Y_UNIT_TEST_SUITE(GlobalAnalysisTests) diff --git a/yql/essentials/sql/v1/complete/analysis/global/ya.make b/yql/essentials/sql/v1/complete/analysis/global/ya.make index b37fa3faa22..1f1670a9675 100644 --- a/yql/essentials/sql/v1/complete/analysis/global/ya.make +++ b/yql/essentials/sql/v1/complete/analysis/global/ya.make @@ -1,6 +1,7 @@ LIBRARY() SRCS( + column.cpp evaluate.cpp function.cpp global.cpp diff --git a/yql/essentials/sql/v1/complete/analysis/local/local.cpp b/yql/essentials/sql/v1/complete/analysis/local/local.cpp index b7e97b51e59..1b732ae5db7 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/local.cpp +++ b/yql/essentials/sql/v1/complete/analysis/local/local.cpp @@ -104,6 +104,7 @@ namespace NSQLComplete { result.Hint = HintMatch(candidates); result.Object = ObjectMatch(context, candidates); result.Cluster = ClusterMatch(context, candidates); + result.Column = ColumnMatch(candidates); result.Binding = BindingMatch(candidates); return result; @@ -296,6 +297,10 @@ namespace NSQLComplete { return cluster; } + bool ColumnMatch(const TC3Candidates& candidates) const { + return AnyOf(candidates.Rules, RuleAdapted(IsLikelyColumnStack)); + } + bool BindingMatch(const TC3Candidates& candidates) const { return AnyOf(candidates.Rules, RuleAdapted(IsLikelyBindingStack)); } diff --git a/yql/essentials/sql/v1/complete/analysis/local/local.h b/yql/essentials/sql/v1/complete/analysis/local/local.h index 5557d6b6dc0..6cf6fc33c51 100644 --- a/yql/essentials/sql/v1/complete/analysis/local/local.h +++ b/yql/essentials/sql/v1/complete/analysis/local/local.h @@ -55,6 +55,7 @@ namespace NSQLComplete { TMaybe<THint> Hint; TMaybe<TObject> Object; TMaybe<TCluster> Cluster; + bool Column = false; bool Binding = false; TEditRange EditRange; }; 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 9837e404bd6..8ac4ff17999 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 @@ -123,6 +123,10 @@ namespace NSQLComplete { return Contains({RULE(Cluster_expr)}, stack); } + bool IsLikelyColumnStack(const TParserCallStack& stack) { + return Contains({RULE(Result_column)}, stack); + } + bool IsLikelyBindingStack(const TParserCallStack& stack) { return EndsWith({RULE(Bind_parameter), RULE(An_id_or_type)}, 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 c8daf9114fe..a0c479a8be0 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 @@ -21,6 +21,8 @@ namespace NSQLComplete { bool IsLikelyClusterStack(const TParserCallStack& stack); + bool IsLikelyColumnStack(const TParserCallStack& stack); + bool IsLikelyBindingStack(const TParserCallStack& stack); TMaybe<EStatementKind> StatementKindOf(const TParserCallStack& stack); diff --git a/yql/essentials/sql/v1/complete/core/name.cpp b/yql/essentials/sql/v1/complete/core/name.cpp new file mode 100644 index 00000000000..65c28e51176 --- /dev/null +++ b/yql/essentials/sql/v1/complete/core/name.cpp @@ -0,0 +1,8 @@ +#include "name.h" + +#include <util/stream/output.h> + +template <> +void Out<NSQLComplete::TTableId>(IOutputStream& out, const NSQLComplete::TTableId& value) { + out << value.Cluster << ".`" << value.Path << "`"; +} diff --git a/yql/essentials/sql/v1/complete/core/name.h b/yql/essentials/sql/v1/complete/core/name.h index 02524766de1..3eac47f3871 100644 --- a/yql/essentials/sql/v1/complete/core/name.h +++ b/yql/essentials/sql/v1/complete/core/name.h @@ -1,5 +1,7 @@ #pragma once +#include <util/generic/string.h> + namespace NSQLComplete { enum class EObjectKind { @@ -7,4 +9,11 @@ namespace NSQLComplete { Table, }; + struct TTableId { + TString Cluster; + TString Path; + + friend bool operator==(const TTableId& lhs, const TTableId& rhs) = default; + }; + } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/core/statement.cpp b/yql/essentials/sql/v1/complete/core/statement.cpp new file mode 100644 index 00000000000..c93e5a7f803 --- /dev/null +++ b/yql/essentials/sql/v1/complete/core/statement.cpp @@ -0,0 +1 @@ +#include "statement.h" diff --git a/yql/essentials/sql/v1/complete/core/ya.make b/yql/essentials/sql/v1/complete/core/ya.make index 599810301ca..0cd0776b11e 100644 --- a/yql/essentials/sql/v1/complete/core/ya.make +++ b/yql/essentials/sql/v1/complete/core/ya.make @@ -3,6 +3,8 @@ LIBRARY() SRCS( environment.cpp input.cpp + name.cpp + statement.cpp ) PEERDIR( diff --git a/yql/essentials/sql/v1/complete/name/object/schema.h b/yql/essentials/sql/v1/complete/name/object/schema.h index b9aa720d36f..d779b293674 100644 --- a/yql/essentials/sql/v1/complete/name/object/schema.h +++ b/yql/essentials/sql/v1/complete/name/object/schema.h @@ -41,12 +41,26 @@ namespace NSQLComplete { TVector<TFolderEntry> Entries; }; + struct TDescribeTableRequest { + TString TableCluster; + TString TablePath; + TString ColumnPrefix; + size_t ColumnsLimit = 128; // TODO: introduce default limit constant + }; + + struct TDescribeTableResponse { + bool IsExisting = false; + TVector<TString> Columns; + }; + class ISchema: public TThrRefBase { public: using TPtr = TIntrusivePtr<ISchema>; - ~ISchema() override = default; virtual NThreading::TFuture<TListResponse> List(const TListRequest& request) const = 0; + + virtual NThreading::TFuture<TDescribeTableResponse> + Describe(const TDescribeTableRequest& request) const = 0; }; } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/object/simple/schema.cpp b/yql/essentials/sql/v1/complete/name/object/simple/schema.cpp index a9dd20ada99..043464bd73e 100644 --- a/yql/essentials/sql/v1/complete/name/object/simple/schema.cpp +++ b/yql/essentials/sql/v1/complete/name/object/simple/schema.cpp @@ -8,7 +8,7 @@ namespace NSQLComplete { class TSimpleSchema: public ISchema { private: - static auto FilterByName(TString name) { + static auto FilterEntriesByName(TString name) { return [name = std::move(name)](auto f) { TVector<TFolderEntry> entries = f.ExtractValue(); EraseIf(entries, [prefix = ToLowerUTF8(name)](const TFolderEntry& entry) { @@ -18,7 +18,7 @@ namespace NSQLComplete { }; } - static auto FilterByTypes(TMaybe<THashSet<TString>> types) { + static auto FilterEntriesByTypes(TMaybe<THashSet<TString>> types) { return [types = std::move(types)](auto f) mutable { TVector<TFolderEntry> entries = f.ExtractValue(); EraseIf(entries, [types = std::move(types)](const TFolderEntry& entry) { @@ -28,7 +28,7 @@ namespace NSQLComplete { }; } - static auto Crop(size_t limit) { + static auto CropEntries(size_t limit) { return [limit](auto f) { TVector<TFolderEntry> entries = f.ExtractValue(); entries.crop(limit); @@ -36,7 +36,7 @@ namespace NSQLComplete { }; } - static auto ToResponse(TStringBuf name) { + static auto ToListResponse(TStringBuf name) { const auto length = name.length(); return [length](auto f) { return TListResponse{ @@ -46,6 +46,38 @@ namespace NSQLComplete { }; } + static auto FilterColumnsByName(TString name) { + return [name = std::move(name)](auto f) { + return f.ExtractValue().Transform([&](auto&& table) { + EraseIf(table.Columns, [prefix = ToLowerUTF8(name)](const TString& name) { + return !name.StartsWith(prefix); + }); + return table; + }); + }; + } + + static auto CropColumns(size_t limit) { + return [limit](auto f) { + return f.ExtractValue().Transform([&](auto&& table) { + table.Columns.crop(limit); + return table; + }); + }; + } + + static auto ToTableDescribeResponse() { + return [](auto f) { + TMaybe<TTableDetails> table = f.ExtractValue(); + return TDescribeTableResponse{ + .IsExisting = table.Defined(), + .Columns = table + .Transform([](auto&& table) { return table.Columns; }) + .GetOrElse({}), + }; + }; + } + public: explicit TSimpleSchema(ISimpleSchema::TPtr simple) : Simple_(std::move(simple)) @@ -55,10 +87,19 @@ namespace NSQLComplete { NThreading::TFuture<TListResponse> List(const TListRequest& request) const override { auto [path, name] = Simple_->Split(request.Path); return Simple_->List(request.Cluster, TString(path)) - .Apply(FilterByName(TString(name))) - .Apply(FilterByTypes(request.Filter.Types)) - .Apply(Crop(request.Limit)) - .Apply(ToResponse(name)); + .Apply(FilterEntriesByName(TString(name))) + .Apply(FilterEntriesByTypes(request.Filter.Types)) + .Apply(CropEntries(request.Limit)) + .Apply(ToListResponse(name)); + } + + NThreading::TFuture<TDescribeTableResponse> + Describe(const TDescribeTableRequest& request) const override { + return Simple_ + ->DescribeTable(request.TableCluster, request.TablePath) + .Apply(FilterColumnsByName(TString(request.ColumnPrefix))) + .Apply(CropColumns(request.ColumnsLimit)) + .Apply(ToTableDescribeResponse()); } private: @@ -77,6 +118,12 @@ namespace NSQLComplete { return List(std::move(folder)); } + NThreading::TFuture<TMaybe<TTableDetails>> + ISimpleSchema::DescribeTable(const TString& cluster, const TString& path) const { + Y_UNUSED(cluster, path); + return NThreading::MakeFuture<TMaybe<TTableDetails>>(Nothing()); + } + ISchema::TPtr MakeSimpleSchema(ISimpleSchema::TPtr simple) { return ISchema::TPtr(new TSimpleSchema(std::move(simple))); } diff --git a/yql/essentials/sql/v1/complete/name/object/simple/schema.h b/yql/essentials/sql/v1/complete/name/object/simple/schema.h index e3b5cb4cb1b..2bdb428b191 100644 --- a/yql/essentials/sql/v1/complete/name/object/simple/schema.h +++ b/yql/essentials/sql/v1/complete/name/object/simple/schema.h @@ -9,6 +9,10 @@ namespace NSQLComplete { TStringBuf NameHint; }; + struct TTableDetails { + TVector<TString> Columns; + }; + class ISimpleSchema: public TThrRefBase { public: using TPtr = TIntrusivePtr<ISimpleSchema>; @@ -22,6 +26,9 @@ namespace NSQLComplete { virtual NThreading::TFuture<TVector<TFolderEntry>> List(TString cluster, TString folder) const; + + virtual NThreading::TFuture<TMaybe<TTableDetails>> + DescribeTable(const TString& cluster, const TString& path) const; }; ISchema::TPtr MakeSimpleSchema(ISimpleSchema::TPtr simple); diff --git a/yql/essentials/sql/v1/complete/name/object/simple/static/schema.cpp b/yql/essentials/sql/v1/complete/name/object/simple/static/schema.cpp index 3482da8a332..7707d8dc4ba 100644 --- a/yql/essentials/sql/v1/complete/name/object/simple/static/schema.cpp +++ b/yql/essentials/sql/v1/complete/name/object/simple/static/schema.cpp @@ -1,20 +1,32 @@ #include "schema.h" +#include <library/cpp/iterator/concatenate.h> + namespace NSQLComplete { namespace { class TSimpleSchema: public ISimpleSchema { public: - explicit TSimpleSchema(THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> data) - : Data_(std::move(data)) + explicit TSimpleSchema( + THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> folders, + THashMap<TString, THashMap<TString, TTableDetails>> tables) + : Folders_(std::move(folders)) + , Tables_(std::move(tables)) { - for (const auto& [_, tables] : Data_) { - for (const auto& [k, _] : tables) { + for (const auto& [_, paths] : Folders_) { + for (const auto& [k, _] : paths) { Y_ENSURE(k.StartsWith("/"), k << " must start with the '/'"); Y_ENSURE(k.EndsWith("/"), k << " must end with the '/'"); } } + + for (const auto& [_, paths] : Tables_) { + for (const auto& [k, _] : paths) { + Y_ENSURE(k.StartsWith("/"), k << " must start with the '/'"); + Y_ENSURE(!k.EndsWith("/"), k << " must not end with the '/'"); + } + } } TSplittedPath Split(TStringBuf path) const override { @@ -37,7 +49,7 @@ namespace NSQLComplete { const THashMap<TString, TVector<TFolderEntry>>* tables = nullptr; const TVector<TFolderEntry>* items = nullptr; - if ((tables = Data_.FindPtr(cluster)) && + if ((tables = Folders_.FindPtr(cluster)) && (items = tables->FindPtr(folder))) { entries = *items; } @@ -45,15 +57,32 @@ namespace NSQLComplete { return NThreading::MakeFuture(std::move(entries)); } + NThreading::TFuture<TMaybe<TTableDetails>> + DescribeTable(const TString& cluster, const TString& path) const override { + auto* tables = Tables_.FindPtr(cluster); + if (tables == nullptr) { + return NThreading::MakeFuture<TMaybe<TTableDetails>>(Nothing()); + } + + auto* details = tables->FindPtr(path); + if (details == nullptr) { + return NThreading::MakeFuture<TMaybe<TTableDetails>>(Nothing()); + } + + return NThreading::MakeFuture<TMaybe<TTableDetails>>(*details); + } + private: - THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> Data_; + THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> Folders_; + THashMap<TString, THashMap<TString, TTableDetails>> Tables_; }; } // namespace ISimpleSchema::TPtr MakeStaticSimpleSchema( - THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> fs) { - return new TSimpleSchema(std::move(fs)); + THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> folders, + THashMap<TString, THashMap<TString, TTableDetails>> tables) { + return new TSimpleSchema(std::move(folders), std::move(tables)); } } // namespace NSQLComplete diff --git a/yql/essentials/sql/v1/complete/name/object/simple/static/schema.h b/yql/essentials/sql/v1/complete/name/object/simple/static/schema.h index 009b433ee4a..57fb322d6e2 100644 --- a/yql/essentials/sql/v1/complete/name/object/simple/static/schema.h +++ b/yql/essentials/sql/v1/complete/name/object/simple/static/schema.h @@ -5,6 +5,7 @@ namespace NSQLComplete { ISimpleSchema::TPtr MakeStaticSimpleSchema( - THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> fs); + THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> folders, + THashMap<TString, THashMap<TString, TTableDetails>> tables = {}); } // namespace NSQLComplete 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 7b7bc9f9755..5139b27dfa3 100644 --- a/yql/essentials/sql/v1/complete/name/service/name_service.h +++ b/yql/essentials/sql/v1/complete/name/service/name_service.h @@ -66,6 +66,12 @@ namespace NSQLComplete { struct TConstraints: TNamespaced {}; }; + struct TColumnName: TIndentifier { + struct TConstraints { + TVector<TTableId> Tables; + }; + }; + struct TBindingName: TIndentifier { }; @@ -83,6 +89,7 @@ namespace NSQLComplete { TFolderName, TTableName, TClusterName, + TColumnName, TBindingName, TUnkownName>; @@ -93,6 +100,7 @@ namespace NSQLComplete { TMaybe<THintName::TConstraints> Hint; TMaybe<TObjectNameConstraints> Object; TMaybe<TClusterName::TConstraints> Cluster; + TMaybe<TColumnName::TConstraints> Column; bool IsEmpty() const { return !Pragma && @@ -100,7 +108,8 @@ namespace NSQLComplete { !Function && !Hint && !Object && - !Cluster; + !Cluster && + !Column; } TGenericName Qualified(TGenericName unqualified) const; 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 9f30ead37d5..d8e0e0ff1bb 100644 --- a/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp +++ b/yql/essentials/sql/v1/complete/name/service/ranking/ranking.cpp @@ -92,7 +92,8 @@ namespace NSQLComplete { } if constexpr (std::is_same_v<T, TFolderName> || - std::is_same_v<T, TTableName>) { + std::is_same_v<T, TTableName> || + std::is_same_v<T, TColumnName>) { return std::numeric_limits<size_t>::max(); } diff --git a/yql/essentials/sql/v1/complete/name/service/schema/name_service.cpp b/yql/essentials/sql/v1/complete/name/service/schema/name_service.cpp index b21ad828317..2970f06d7cd 100644 --- a/yql/essentials/sql/v1/complete/name/service/schema/name_service.cpp +++ b/yql/essentials/sql/v1/complete/name/service/schema/name_service.cpp @@ -12,13 +12,26 @@ namespace NSQLComplete { } NThreading::TFuture<TNameResponse> Lookup(TNameRequest request) const override { - if (!request.Constraints.Object) { - return NThreading::MakeFuture<TNameResponse>({}); + if (request.Constraints.Object) { + return Schema_ + ->List(ToListRequest(std::move(request))) + .Apply(ToListNameResponse); } - return Schema_ - ->List(ToListRequest(std::move(request))) - .Apply(ToNameResponse); + if (request.Constraints.Column) { + Y_ENSURE(request.Constraints.Column->Tables.size() == 1, "Not Implemented"); + TTableId table = request.Constraints.Column->Tables[0]; + return Schema_ + ->Describe({ + .TableCluster = table.Cluster, + .TablePath = table.Path, + .ColumnPrefix = request.Prefix, + .ColumnsLimit = request.Limit, + }) + .Apply(ToDescribeNameResponse); + } + + return NThreading::MakeFuture<TNameResponse>({}); } private: @@ -53,7 +66,7 @@ namespace NSQLComplete { } } - static TNameResponse ToNameResponse(NThreading::TFuture<TListResponse> f) { + static TNameResponse ToListNameResponse(NThreading::TFuture<TListResponse> f) { TListResponse list = f.ExtractValue(); TNameResponse response; @@ -83,6 +96,18 @@ namespace NSQLComplete { return name; } + static TNameResponse ToDescribeNameResponse(NThreading::TFuture<TDescribeTableResponse> f) { + TDescribeTableResponse table = f.ExtractValue(); + + TNameResponse response; + for (TString& column : table.Columns) { + TColumnName name; + name.Indentifier = std::move(column); + response.RankedNames.emplace_back(std::move(name)); + } + return response; + } + ISchema::TPtr Schema_; }; diff --git a/yql/essentials/sql/v1/complete/sql_complete.cpp b/yql/essentials/sql/v1/complete/sql_complete.cpp index eea7571e8f1..2f61b1fe4df 100644 --- a/yql/essentials/sql/v1/complete/sql_complete.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete.cpp @@ -142,6 +142,11 @@ namespace NSQLComplete { object->Kinds.emplace(EObjectKind::Table); } + if (context.Column && global.Column) { + request.Constraints.Column = TColumnName::TConstraints(); + request.Constraints.Column->Tables = std::move(global.Column->Tables); + } + return request; } @@ -231,6 +236,10 @@ namespace NSQLComplete { return {ECandidateKind::ClusterName, std::move(name.Indentifier)}; } + if constexpr (std::is_base_of_v<TColumnName, T>) { + return {ECandidateKind::ColumnName, std::move(name.Indentifier)}; + } + if constexpr (std::is_base_of_v<TBindingName, T>) { if (!context.Binding) { name.Indentifier.prepend('$'); @@ -333,6 +342,9 @@ void Out<NSQLComplete::ECandidateKind>(IOutputStream& out, NSQLComplete::ECandid case NSQLComplete::ECandidateKind::BindingName: out << "BindingName"; break; + case NSQLComplete::ECandidateKind::ColumnName: + out << "ColumnName"; + 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 df5159aff3c..33ca0c00ac9 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, + ColumnName, 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 878897a3844..d37d34be06b 100644 --- a/yql/essentials/sql/v1/complete/sql_complete_ut.cpp +++ b/yql/essentials/sql/v1/complete/sql_complete_ut.cpp @@ -43,6 +43,7 @@ public: Y_UNIT_TEST_SUITE(SqlCompleteTests) { using ECandidateKind::BindingName; using ECandidateKind::ClusterName; + using ECandidateKind::ColumnName; using ECandidateKind::FolderName; using ECandidateKind::FunctionName; using ECandidateKind::HintName; @@ -83,7 +84,7 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { }, }; - THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> fs = { + THashMap<TString, THashMap<TString, TVector<TFolderEntry>>> folders = { {"", {{"/", {{"Folder", "local"}, {"Folder", "test"}, {"Folder", "prod"}, @@ -103,15 +104,18 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { {{"/", {{"Table", "maxim"}}}}}, }; + THashMap<TString, THashMap<TString, TTableDetails>> tables = { + {"example", {{"/people", {{"name", "age"}}}}}}; + auto clustersIt = NFuncTools::Filter( - [](const auto& x) { return !x.empty(); }, IterateKeys(fs)); + [](const auto& x) { return !x.empty(); }, IterateKeys(folders)); TVector<TString> clusters(begin(clustersIt), end(clustersIt)); TFrequencyData frequency; TVector<INameService::TPtr> children = { MakeStaticNameService(std::move(names), frequency), - MakeSchemaNameService(MakeSimpleSchema(MakeStaticSimpleSchema(std::move(fs)))), + MakeSchemaNameService(MakeSimpleSchema(MakeStaticSimpleSchema(std::move(folders), std::move(tables)))), MakeClusterNameService(MakeStaticClusterDiscovery(std::move(clusters))), }; INameService::TPtr service = MakeUnionNameService( @@ -1045,6 +1049,31 @@ Y_UNIT_TEST_SUITE(SqlCompleteTests) { CompleteTop(1, engine, "SELECT Max(#)").at(0).Kind, FolderName); } + Y_UNIT_TEST(ColumnsAtSimpleSelect) { + auto engine = MakeSqlCompletionEngineUT(); + { + TVector<TCandidate> expected = { + {ColumnName, "age"}, + {ColumnName, "name"}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "SELECT # FROM example.`/people`"), expected); + } + { + TVector<TCandidate> expected = { + {ColumnName, "age"}, + {Keyword, "ALL"}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "SELECT a# FROM example.`/people`"), expected); + } + { + TVector<TCandidate> expected = { + {ColumnName, "age"}, + {ColumnName, "name"}, + }; + UNIT_ASSERT_VALUES_EQUAL(CompleteTop(2, engine, "USE example; SELECT # FROM `/people`"), expected); + } + } + Y_UNIT_TEST(Typing) { const auto queryUtf16 = TUtf16String::FromUtf8( "SELECT \n" diff --git a/yql/essentials/sql/v1/complete/syntax/ya.make b/yql/essentials/sql/v1/complete/syntax/ya.make index 6a35a80375e..f60a05f7b59 100644 --- a/yql/essentials/sql/v1/complete/syntax/ya.make +++ b/yql/essentials/sql/v1/complete/syntax/ya.make @@ -14,6 +14,7 @@ PEERDIR( yql/essentials/sql/settings yql/essentials/sql/v1/lexer yql/essentials/sql/v1/reflect + yql/essentials/sql/v1/complete/antlr4 yql/essentials/sql/v1/complete/core yql/essentials/sql/v1/complete/text ) |