diff options
| author | Victor Smirnov <[email protected]> | 2025-05-26 16:44:52 +0300 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-05-26 16:44:52 +0300 |
| commit | f6d9db9582cd1b113a52656f9077c466185f8fe5 (patch) | |
| tree | 480430fb29809e6defd03582a997d1968e502e50 | |
| parent | 04801c0ba7d7f9f55f7bd7681dd03a87c221d8c2 (diff) | |
#9056 Complete object names in YDB CLI interactive mode (#18146)
Signed-off-by: vityaman <[email protected]>
11 files changed, 316 insertions, 39 deletions
diff --git a/ydb/apps/ydb/CHANGELOG.md b/ydb/apps/ydb/CHANGELOG.md index 7fcd5b67e7a..14f4b645dae 100644 --- a/ydb/apps/ydb/CHANGELOG.md +++ b/ydb/apps/ydb/CHANGELOG.md @@ -1,3 +1,4 @@ +* Added object names completion in interactive mode ## 2.21.0 ## diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.cpp b/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.cpp new file mode 100644 index 00000000000..1621f5f4236 --- /dev/null +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.cpp @@ -0,0 +1,50 @@ +#include "dummy_name_service.h" + +#include <library/cpp/iterator/functools.h> + +namespace NYdb::NConsoleClient { + + namespace { + + class TNameService: public NSQLComplete::INameService { + public: + NThreading::TFuture<NSQLComplete::TNameResponse> + Lookup(NSQLComplete::TNameRequest request) const override { + NSQLComplete::TNameResponse response; + + if (!request.Constraints.Object) { + return NThreading::MakeFuture(std::move(response)); + } + + auto kinds = request.Constraints.Object->Kinds; + if (kinds.contains(NSQLComplete::EObjectKind::Folder)) { + response.RankedNames.emplace_back(Folder()); + } + if (kinds.contains(NSQLComplete::EObjectKind::Table)) { + response.RankedNames.emplace_back(Table()); + } + + return NThreading::MakeFuture(std::move(response)); + } + + private: + static NSQLComplete::TFolderName Folder() { + NSQLComplete::TFolderName name; + name.Indentifier = "folder_name"; + return std::move(name); + } + + static NSQLComplete::TTableName Table() { + NSQLComplete::TTableName name; + name.Indentifier = "table_name"; + return std::move(name); + } + }; + + } // namespace + + NSQLComplete::INameService::TPtr MakeDummyNameService() { + return new TNameService(); + } + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.h b/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.h new file mode 100644 index 00000000000..14736000752 --- /dev/null +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.h @@ -0,0 +1,9 @@ +#pragma once + +#include <yql/essentials/sql/v1/complete/name/service/name_service.h> + +namespace NYdb::NConsoleClient { + + NSQLComplete::INameService::TPtr MakeDummyNameService(); + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/ya.make b/ydb/public/lib/ydb_cli/commands/interactive/complete/ya.make index 4f272c7d8a4..4dc8df234d5 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/complete/ya.make +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/ya.make @@ -1,15 +1,23 @@ LIBRARY() SRCS( + dummy_name_service.cpp + ydb_schema.cpp yql_completer.cpp ) PEERDIR( contrib/restricted/patched/replxx yql/essentials/sql/v1/complete + yql/essentials/sql/v1/complete/name/object + yql/essentials/sql/v1/complete/name/object/simple + yql/essentials/sql/v1/complete/name/service/schema + yql/essentials/sql/v1/complete/name/service/static + yql/essentials/sql/v1/complete/name/service/union yql/essentials/sql/v1/lexer/antlr4_pure yql/essentials/sql/v1/lexer/antlr4_pure_ansi ydb/public/lib/ydb_cli/commands/interactive/highlight/color + ydb/public/lib/ydb_cli/common ) END() diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.cpp b/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.cpp new file mode 100644 index 00000000000..f8d0dd4d8a1 --- /dev/null +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.cpp @@ -0,0 +1,122 @@ +#include "ydb_schema.h" + +#include <ydb/public/lib/ydb_cli/commands/ydb_command.h> + +#include <yql/essentials/sql/v1/complete/name/object/simple/schema.h> + +namespace NYdb::NConsoleClient { + + class TYDBSchema: public NSQLComplete::ISimpleSchema { + public: + explicit TYDBSchema(TDriver driver, TString database, bool isVerbose) + : Driver_(std::move(driver)) + , Database_(std::move(database)) + , IsVerbose_(isVerbose) + { + } + + NSQLComplete::TSplittedPath Split(TStringBuf path) const override { + size_t pos = path.find_last_of('/'); + if (pos == TString::npos) { + return {"", path}; + } + + TStringBuf head, tail; + TStringBuf(path).SplitAt(pos + 1, head, tail); + return {head, tail}; + } + + NThreading::TFuture<TVector<NSQLComplete::TFolderEntry>> List(TString folder) const override { + return NScheme::TSchemeClient(Driver_) + .ListDirectory(Qualified(folder)) + .Apply([this, folder](auto f) { return this->Convert(folder, f.ExtractValue()); }); + } + + private: + TString Qualified(TString folder) const { + if (!folder.StartsWith('/')) { + folder.prepend('/'); + folder.prepend(Database_); + } + return folder; + } + + TVector<NSQLComplete::TFolderEntry> Convert(TString folder, NScheme::TListDirectoryResult result) const { + if (!result.IsSuccess()) { + if (IsVerbose_) { + Cerr << "ListDirectory('" << folder << "') failed: " + << result.GetIssues().ToOneLineString(); + } + return {}; + } + + return Convert(result.GetChildren()); + } + + static TVector<NSQLComplete::TFolderEntry> Convert(const std::vector<NScheme::TSchemeEntry>& children) { + TVector<NSQLComplete::TFolderEntry> entries; + entries.reserve(children.size()); + for (size_t i = 0; i < children.size(); ++i) { + entries.emplace_back(Convert(children[i])); + } + return entries; + } + + static NSQLComplete::TFolderEntry Convert(const NScheme::TSchemeEntry& entry) { + return { + .Type = Convert(entry.Type), + .Name = TString(entry.Name), + }; + } + + static TString Convert(NScheme::ESchemeEntryType type) { + switch (type) { + case NScheme::ESchemeEntryType::Directory: + return "Folder"; + case NScheme::ESchemeEntryType::Table: + return "Table"; + case NScheme::ESchemeEntryType::PqGroup: + return "PqGroup"; + case NScheme::ESchemeEntryType::SubDomain: + return "SubDomain"; + case NScheme::ESchemeEntryType::RtmrVolume: + return "RtmrVolume"; + case NScheme::ESchemeEntryType::BlockStoreVolume: + return "BlockStoreVolume"; + case NScheme::ESchemeEntryType::CoordinationNode: + return "CoordinationNode"; + case NScheme::ESchemeEntryType::ColumnStore: + return "ColumnStore"; + case NScheme::ESchemeEntryType::ColumnTable: + return "ColumnTable"; + case NScheme::ESchemeEntryType::Sequence: + return "Sequence"; + case NScheme::ESchemeEntryType::Replication: + return "Replication"; + case NScheme::ESchemeEntryType::Topic: + return "Topic"; + case NScheme::ESchemeEntryType::ExternalTable: + return "ExternalTable"; + case NScheme::ESchemeEntryType::ExternalDataSource: + return "ExternalDataSource"; + case NScheme::ESchemeEntryType::View: + return "View"; + case NScheme::ESchemeEntryType::ResourcePool: + return "ResourcePool"; + case NScheme::ESchemeEntryType::Unknown: + default: + return "Unknown"; + } + } + + TDriver Driver_; + TString Database_; + bool IsVerbose_; + }; + + NSQLComplete::ISimpleSchema::TPtr MakeYDBSchema( + TDriver driver, TString database, bool isVerbose) { + return new TYDBSchema(std::move(driver), std::move(database), isVerbose); + } + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.h b/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.h new file mode 100644 index 00000000000..024059f524f --- /dev/null +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.h @@ -0,0 +1,13 @@ +#pragma once + +#include <ydb/public/lib/ydb_cli/common/command.h> + +#include <yql/essentials/sql/v1/complete/name/object/simple/schema.h> + +#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/driver/driver.h> + +namespace NYdb::NConsoleClient { + + NSQLComplete::ISimpleSchema::TPtr MakeYDBSchema(TDriver driver, TString database, bool isVerbose); + +} // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.cpp b/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.cpp index f1ea8174eba..e8331999541 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.cpp +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.cpp @@ -1,9 +1,15 @@ #include "yql_completer.h" +#include <ydb/public/lib/ydb_cli/commands/interactive/complete/dummy_name_service.h> +#include <ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.h> #include <ydb/public/lib/ydb_cli/commands/interactive/highlight/color/schema.h> #include <yql/essentials/sql/v1/complete/sql_complete.h> -#include <yql/essentials/sql/v1/complete/name/static/name_service.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> +#include <yql/essentials/sql/v1/complete/text/word.h> + #include <yql/essentials/sql/v1/lexer/antlr4_pure/lexer.h> #include <yql/essentials/sql/v1/lexer/antlr4_pure_ansi/lexer.h> @@ -15,36 +21,76 @@ namespace NYdb::NConsoleClient { public: using TPtr = THolder<IYQLCompleter>; - explicit TYQLCompleter(NSQLComplete::ISqlCompletionEngine::TPtr engine, TColorSchema color) - : Engine(std::move(engine)) + TYQLCompleter( + NSQLComplete::ISqlCompletionEngine::TPtr heavyEngine, + NSQLComplete::ISqlCompletionEngine::TPtr lightEngine, + TColorSchema color) + : HeavyEngine(std::move(heavyEngine)) + , LightEngine(std::move(lightEngine)) , Color(std::move(color)) { } - TCompletions Apply(const std::string& prefix, int& contextLen) override { - auto completion = Engine->Complete({ - .Text = prefix, + TCompletions ApplyHeavy(TStringBuf text, const std::string& prefix, int& contextLen) override { + return Apply(text, prefix, contextLen, /* light = */ false); + } + + THints ApplyLight(TStringBuf text, const std::string& prefix, int& contextLen) override { + replxx::Replxx::hints_t hints; + for (auto& candidate : Apply(text, prefix, contextLen, /* light = */ true)) { + hints.emplace_back(std::move(candidate.text())); + } + return hints; + } + + private: + TCompletions Apply(TStringBuf text, const std::string& prefix, int& contextLen, bool light) { + NSQLComplete::TCompletionInput input = { + .Text = text, .CursorPosition = prefix.length(), - }); + }; + + auto completion = GetEngine(light)->CompleteAsync(input).ExtractValueSync(); contextLen = GetNumberOfUTF8Chars(completion.CompletedToken.Content); + return ReplxxCompletionsOf(std::move(completion.Candidates)); + } + + NSQLComplete::ISqlCompletionEngine::TPtr& GetEngine(bool light) { + if (light) { + return LightEngine; + } + return HeavyEngine; + } + + replxx::Replxx::completions_t ReplxxCompletionsOf(TVector<NSQLComplete::TCandidate> candidates) const { replxx::Replxx::completions_t entries; - for (auto& candidate : completion.Candidates) { - const auto back = candidate.Content.back(); - if (!IsLeftPunct(back) && back != '<' || IsQuotation(back)) { - candidate.Content += ' '; + entries.reserve(candidates.size()); + for (auto& candidate : candidates) { + if (candidate.Kind == NSQLComplete::ECandidateKind::FolderName && + candidate.Content.EndsWith('`')) { + candidate.Content.pop_back(); } - - entries.emplace_back( - std::move(candidate.Content), - ReplxxColorOf(candidate.Kind)); + entries.emplace_back(ReplxxCompletionOf(std::move(candidate))); } return entries; } - private: - replxx::Replxx::Color ReplxxColorOf(NSQLComplete::ECandidateKind kind) { + replxx::Replxx::Completion ReplxxCompletionOf(NSQLComplete::TCandidate candidate) const { + const auto back = candidate.Content.back(); + if ( + !(candidate.Kind == NSQLComplete::ECandidateKind::FolderName || candidate.Kind == NSQLComplete::ECandidateKind::TableName) && (!IsLeftPunct(back) && back != '<' || IsQuotation(back))) { + candidate.Content += ' '; + } + + return { + std::move(candidate.Content), + ReplxxColorOf(candidate.Kind), + }; + } + + replxx::Replxx::Color ReplxxColorOf(NSQLComplete::ECandidateKind kind) const { switch (kind) { case NSQLComplete::ECandidateKind::Keyword: return Color.keyword; @@ -52,12 +98,16 @@ namespace NYdb::NConsoleClient { return Color.identifier.type; case NSQLComplete::ECandidateKind::FunctionName: return Color.identifier.function; + case NSQLComplete::ECandidateKind::FolderName: + case NSQLComplete::ECandidateKind::TableName: + return Color.identifier.quoted; default: return replxx::Replxx::Color::DEFAULT; } } - NSQLComplete::ISqlCompletionEngine::TPtr Engine; + NSQLComplete::ISqlCompletionEngine::TPtr HeavyEngine; + NSQLComplete::ISqlCompletionEngine::TPtr LightEngine; TColorSchema Color; }; @@ -72,18 +122,34 @@ namespace NYdb::NConsoleClient { }; } - IYQLCompleter::TPtr MakeYQLCompleter(TColorSchema color) { + IYQLCompleter::TPtr MakeYQLCompleter( + TColorSchema color, TDriver driver, TString database, bool isVerbose) { NSQLComplete::TLexerSupplier lexer = MakePureLexerSupplier(); - NSQLComplete::NameSet names = NSQLComplete::MakeDefaultNameSet(); - NSQLComplete::IRanking::TPtr ranking = NSQLComplete::MakeDefaultRanking(); - NSQLComplete::INameService::TPtr service = - MakeStaticNameService(std::move(names), std::move(ranking)); + auto statics = NSQLComplete::MakeStaticNameService( + NSQLComplete::MakeDefaultNameSet(), ranking); + + TVector<NSQLComplete::INameService::TPtr> heavies = { + statics, + NSQLComplete::MakeSchemaNameService( + NSQLComplete::MakeSimpleSchema( + MakeYDBSchema(std::move(driver), std::move(database), isVerbose))), + }; + + TVector<NSQLComplete::INameService::TPtr> lighties = { + statics, + MakeDummyNameService(), + }; return IYQLCompleter::TPtr(new TYQLCompleter( - NSQLComplete::MakeSqlCompletionEngine(std::move(lexer), std::move(service)), + NSQLComplete::MakeSqlCompletionEngine( + lexer, + NSQLComplete::MakeUnionNameService(std::move(heavies), ranking)), + NSQLComplete::MakeSqlCompletionEngine( + lexer, + NSQLComplete::MakeUnionNameService(std::move(lighties), ranking)), std::move(color))); } diff --git a/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.h b/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.h index bf97d604976..a3e3e0f03dc 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.h +++ b/ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.h @@ -1,6 +1,9 @@ #pragma once #include <ydb/public/lib/ydb_cli/commands/interactive/highlight/color/schema.h> +#include <ydb/public/lib/ydb_cli/common/command.h> + +#include <ydb/public/sdk/cpp/include/ydb-cpp-sdk/client/driver/driver.h> #include <contrib/restricted/patched/replxx/include/replxx.hxx> @@ -9,15 +12,18 @@ namespace NYdb::NConsoleClient { using TCompletions = replxx::Replxx::completions_t; + using THints = replxx::Replxx::hints_t; class IYQLCompleter { public: using TPtr = THolder<IYQLCompleter>; - virtual TCompletions Apply(const std::string& prefix, int& contextLen) = 0; + virtual TCompletions ApplyHeavy(TStringBuf text, const std::string& prefix, int& contextLen) = 0; + virtual THints ApplyLight(TStringBuf text, const std::string& prefix, int& contextLen) = 0; virtual ~IYQLCompleter() = default; }; - IYQLCompleter::TPtr MakeYQLCompleter(TColorSchema color); + IYQLCompleter::TPtr MakeYQLCompleter( + TColorSchema color, TDriver driver, TString database, bool isVerbose); } // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/interactive_cli.cpp b/ydb/public/lib/ydb_cli/commands/interactive/interactive_cli.cpp index 20524b73452..f0f6565a01c 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/interactive_cli.cpp +++ b/ydb/public/lib/ydb_cli/commands/interactive/interactive_cli.cpp @@ -133,7 +133,7 @@ TInteractiveCLI::TInteractiveCLI(TClientCommand::TConfig& config, std::string pr void TInteractiveCLI::Run() { TFsPath homeDirPath(HomeDir); TString historyFilePath(homeDirPath / ".ydb_history"); - std::unique_ptr<ILineReader> lineReader = CreateLineReader(Prompt, historyFilePath); + std::unique_ptr<ILineReader> lineReader = CreateLineReader(Prompt, historyFilePath, Config); InteractiveCLIState interactiveCLIState; diff --git a/ydb/public/lib/ydb_cli/commands/interactive/line_reader.cpp b/ydb/public/lib/ydb_cli/commands/interactive/line_reader.cpp index ee47f71e2d0..e216d879b26 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/line_reader.cpp +++ b/ydb/public/lib/ydb_cli/commands/interactive/line_reader.cpp @@ -1,7 +1,9 @@ #include "line_reader.h" +#include <ydb/public/lib/ydb_cli/commands/interactive/complete/ydb_schema.h> #include <ydb/public/lib/ydb_cli/commands/interactive/complete/yql_completer.h> #include <ydb/public/lib/ydb_cli/commands/interactive/highlight/yql_highlighter.h> +#include <ydb/public/lib/ydb_cli/commands/ydb_command.h> #include <yql/essentials/sql/v1/complete/sql_complete.h> #include <yql/essentials/sql/v1/complete/string_util.h> @@ -41,7 +43,7 @@ std::optional<FileHandlerLockGuard> LockFile(TFileHandle& fileHandle) { class TLineReader: public ILineReader { public: - TLineReader(std::string prompt, std::string historyFilePath); + TLineReader(std::string prompt, std::string historyFilePath, TClientCommand::TConfig& config); std::optional<std::string> ReadLine() override; @@ -56,11 +58,11 @@ private: replxx::Replxx Rx; }; -TLineReader::TLineReader(std::string prompt, std::string historyFilePath) +TLineReader::TLineReader(std::string prompt, std::string historyFilePath, TClientCommand::TConfig& config) : Prompt(std::move(prompt)) , HistoryFilePath(std::move(historyFilePath)) , HistoryFileHandle(HistoryFilePath.c_str(), EOpenModeFlag::OpenAlways | EOpenModeFlag::RdWr | EOpenModeFlag::AW | EOpenModeFlag::ARUser | EOpenModeFlag::ARGroup) - , YQLCompleter(MakeYQLCompleter(TColorSchema::Monaco())) + , YQLCompleter(MakeYQLCompleter(TColorSchema::Monaco(), TYdbCommand::CreateDriver(config), config.Database, config.IsVerbose())) , YQLHighlighter(MakeYQLHighlighter(TColorSchema::Monaco())) { Rx.install_window_change_handler(); @@ -68,14 +70,10 @@ TLineReader::TLineReader(std::string prompt, std::string historyFilePath) Rx.set_complete_on_empty(true); Rx.set_word_break_characters(NSQLComplete::WordBreakCharacters); Rx.set_completion_callback([this](const std::string& prefix, int& contextLen) { - return YQLCompleter->Apply(prefix, contextLen); + return YQLCompleter->ApplyHeavy(Rx.get_state().text(), prefix, contextLen); }); Rx.set_hint_callback([this](const std::string& prefix, int& contextLen, TColor&) { - replxx::Replxx::hints_t hints; - for (auto& candidate : YQLCompleter->Apply(prefix, contextLen)) { - hints.emplace_back(std::move(candidate.text())); - } - return hints; + return YQLCompleter->ApplyLight(Rx.get_state().text(), prefix, contextLen); }); Rx.set_highlighter_callback([this](const auto& text, auto& colors) { @@ -162,8 +160,9 @@ void TLineReader::AddToHistory(const std::string& line) { } // namespace -std::unique_ptr<ILineReader> CreateLineReader(std::string prompt, std::string historyFilePath) { - return std::make_unique<TLineReader>(std::move(prompt), std::move(historyFilePath)); +std::unique_ptr<ILineReader> CreateLineReader( + std::string prompt, std::string historyFilePath, TClientCommand::TConfig& config) { + return std::make_unique<TLineReader>(std::move(prompt), std::move(historyFilePath), config); } } // namespace NYdb::NConsoleClient diff --git a/ydb/public/lib/ydb_cli/commands/interactive/line_reader.h b/ydb/public/lib/ydb_cli/commands/interactive/line_reader.h index 830cd9f56cf..9c3f3d2f86d 100644 --- a/ydb/public/lib/ydb_cli/commands/interactive/line_reader.h +++ b/ydb/public/lib/ydb_cli/commands/interactive/line_reader.h @@ -4,6 +4,8 @@ #include <optional> #include <string> +#include <ydb/public/lib/ydb_cli/common/command.h> + namespace NYdb::NConsoleClient { class ILineReader { @@ -13,6 +15,7 @@ public: virtual ~ILineReader() = default; }; -std::unique_ptr<ILineReader> CreateLineReader(std::string prompt, std::string historyFilePath); +std::unique_ptr<ILineReader> CreateLineReader( + std::string prompt, std::string historyFilePath, TClientCommand::TConfig& config); } // namespace NYdb::NConsoleClient |
