mysqlshdk/shellcore/provider_sql.cc (709 lines of code) (raw):
/*
* Copyright (c) 2017, 2024, Oracle and/or its affiliates.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2.0,
* as published by the Free Software Foundation.
*
* This program is designed to work with certain software (including
* but not limited to OpenSSL) that is licensed under separate terms,
* as designated in a particular file or component or in included license
* documentation. The authors of MySQL hereby grant you an additional
* permission to link the program and your derivative works with the
* separately licensed software that they have either included with
* the program or referenced in the documentation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
* the GNU General Public License, version 2.0, for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "mysqlshdk/shellcore/provider_sql.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <optional>
#include <set>
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include "mysqlshdk/libs/db/session.h"
#include "mysqlshdk/libs/parser/base/symbol-info.h"
#include "mysqlshdk/libs/parser/server/sql_modes.h"
#include "mysqlshdk/libs/utils/debug.h"
#include "mysqlshdk/libs/utils/logger.h"
#include "mysqlshdk/libs/utils/utils_general.h"
#include "mysqlshdk/libs/utils/utils_sqlstring.h"
#include "mysqlshdk/libs/utils/utils_string.h"
#include "mysqlshdk/libs/utils/version.h"
namespace shcore {
namespace completer {
using mysqlshdk::db::Error;
using mysqlshdk::db::IRow;
using mysqlshdk::utils::Version;
using Columns = mysqlshdk::Sql_completion_result::Columns;
using Names = mysqlshdk::Sql_completion_result::Names;
namespace {
const Version k_current_version{MYSH_VERSION};
struct Instance {
class Object {
public:
Object() = default;
explicit Object(std::wstring n) : m_wide_name(std::move(n)) {}
explicit Object(std::string n) : m_utf8_name(std::move(n)) {
m_wide_name = shcore::utf8_to_wide(m_utf8_name.value());
}
const std::wstring &wide_name() const { return m_wide_name; }
const std::string &name() const {
if (!m_utf8_name) {
m_utf8_name = shcore::wide_to_utf8(m_wide_name);
}
return m_utf8_name.value();
}
private:
std::wstring m_wide_name;
mutable std::optional<std::string> m_utf8_name;
};
using Objects = std::vector<Object>;
struct Table : Object {
using Object::Object;
Objects columns;
};
using Tables = std::vector<Table>;
struct Schema : Object {
using Object::Object;
Objects events;
Objects functions;
Objects procedures;
Tables tables;
Objects triggers;
Tables views;
};
using Schemas = std::vector<Schema>;
Objects charsets;
Objects collations;
Objects engines;
Objects logfile_groups;
Objects plugins;
Schemas schemas;
const Objects *system_functions = nullptr;
Objects system_variables;
Objects tablespaces;
Objects udfs;
Objects users;
Objects user_variables;
void clear() {
charsets.clear();
collations.clear();
engines.clear();
logfile_groups.clear();
plugins.clear();
schemas.clear();
system_functions = nullptr;
system_variables.clear();
tablespaces.clear();
udfs.clear();
users.clear();
user_variables.clear();
}
};
} // namespace
class Provider_sql::Cache final {
public:
Cache() { set_system_functions(k_current_version); }
Cache(const Cache &) = default;
Cache(Cache &&) = default;
Cache &operator=(const Cache &) = default;
Cache &operator=(Cache &&) = default;
~Cache() = default;
void cancel() { m_cancelled = true; }
void refresh_schemas(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
m_cancelled = false;
fetch_schemas(session);
}
void refresh_schema(const std::shared_ptr<mysqlshdk::db::ISession> &session,
const std::string &schema, bool force) {
m_cancelled = false;
// cache schema names if not done yet
if (m_instance.schemas.empty() || force) {
refresh_schemas(session);
}
if (m_instance.engines.empty() || force) {
refresh_static_symbols(session);
}
if (m_instance.users.empty() || force) {
refresh_database_symbols(session);
}
if (schema.empty()) {
return;
}
if (auto s = find(&m_instance.schemas, schema)) {
if (s->tables.empty() || force) {
fetch_tables(session, s);
}
if (s->views.empty() || force) {
fetch_views(session, s);
}
if (s->functions.empty() || force) {
fetch_functions(session, s);
}
if (s->procedures.empty() || force) {
fetch_procedures(session, s);
}
if (s->events.empty() || force) {
fetch_events(session, s);
}
if (s->triggers.empty() || force) {
fetch_triggers(session, s);
}
}
}
Completion_list complete_schema(const std::string &prefix) const {
Completion_list result;
for (const auto &schema : find_prefix_ci(m_instance.schemas, prefix)) {
result.emplace_back(schema->name());
}
return result;
}
Completion_list complete(mysqlshdk::Sql_completion_result &&result) const {
Completion_list list;
const auto add_from_set = [&list](Names *s) {
while (!s->empty()) {
list.emplace_back(std::move(s->extract(s->begin()).value()));
}
};
const auto quote = [&prefix = std::as_const(result.context.prefix)](
const std::string &s, bool as_string) {
if (prefix.quoted_as_identifier) {
return shcore::quote_identifier(s, prefix.quote);
} else if (as_string && prefix.quoted_as_string) {
return shcore::quote_string(s, prefix.quote);
} else {
// we quote here with '`' characters, they are still valid even if
// ANSI_QUOTES is enabled
return shcore::quote_identifier_if_needed(s);
}
};
const auto quote_identifier = ["e](const std::string &s) {
return quote(s, false);
};
const auto quote_string_or_identifier = ["e](const std::string &s) {
return quote(s, true);
};
const auto add_identifiers_and_quote_if_needed =
[&list, "e_identifier](const auto &container) {
for (const auto &item : container) {
list.emplace_back(quote_identifier(item));
}
};
const auto add_identifiers =
[&list, "e_identifier,
&prefix = std::as_const(result.context.prefix.as_identifier)](
const auto &container, bool as_function_call = false) {
for (const auto &item : find_prefix_ci(container, prefix)) {
auto name = quote_identifier(item->name());
if (as_function_call) {
name += '(';
name += ')';
}
list.emplace_back(std::move(name));
}
};
const auto add_strings_or_identifiers =
[&list, "e_string_or_identifier,
&prefix =
std::as_const(result.context.prefix.as_string_or_identifier)](
const auto &container) {
for (const auto &item : find_prefix_ci(container, prefix)) {
list.emplace_back(quote_string_or_identifier(item->name()));
}
};
const auto add_identifiers_from = [&add_identifiers, this](
const Names &schemas, auto source,
bool as_function_call = false) {
for (const auto &schema : schemas) {
if (const auto &s = find(m_instance.schemas, schema)) {
add_identifiers(s->*source, as_function_call);
}
}
};
const auto add_columns = [&add_identifiers, this](const Columns &columns) {
for (const auto &schema : columns) {
if (const auto &s = find(m_instance.schemas, schema.first)) {
for (const auto &table : schema.second) {
if (const auto &t = find(s->tables, table)) {
add_identifiers(t->columns);
}
if (const auto &v = find(s->views, table)) {
add_identifiers(v->columns);
}
}
}
}
};
// these are already filtered
add_from_set(&result.keywords);
add_from_set(&result.system_functions);
add_identifiers_and_quote_if_needed(result.tables);
for (const auto &candidate : result.candidates) {
using Candidate = mysqlshdk::Sql_completion_result::Candidate;
switch (candidate) {
case Candidate::SCHEMA:
add_identifiers(m_instance.schemas);
break;
case Candidate::TABLE:
add_identifiers_from(result.tables_from, &Instance::Schema::tables);
break;
case Candidate::VIEW:
add_identifiers_from(result.views_from, &Instance::Schema::views);
break;
case Candidate::COLUMN:
add_columns(result.columns);
break;
case Candidate::INTERNAL_COLUMN:
add_columns(result.internal_columns);
break;
case Candidate::PROCEDURE:
add_identifiers_from(result.procedures_from,
&Instance::Schema::procedures, true);
break;
case Candidate::FUNCTION:
add_identifiers_from(result.functions_from,
&Instance::Schema::functions, true);
break;
case Candidate::TRIGGER:
add_identifiers_from(result.triggers_from,
&Instance::Schema::triggers);
break;
case Candidate::EVENT:
add_identifiers_from(result.events_from, &Instance::Schema::events);
break;
case Candidate::ENGINE:
add_identifiers(m_instance.engines);
break;
case Candidate::UDF:
add_identifiers(m_instance.udfs, true);
break;
case Candidate::RUNTIME_FUNCTION:
// if prefix was quoted then we cannot match any functions
if (!result.context.prefix.quoted) {
for (const auto &item :
find_prefix_ci(*m_instance.system_functions,
result.context.prefix.full_wide)) {
list.emplace_back(item->name());
}
}
break;
case Candidate::LOGFILE_GROUP:
add_identifiers(m_instance.logfile_groups);
break;
case Candidate::USER_VAR:
// user variables can be quoted as strings or identifiers
add_strings_or_identifiers(m_instance.user_variables);
break;
case Candidate::SYSTEM_VAR:
// system variables can be quoted as identifiers, but if ANSI_QUOTES
// is active, only "sys_var" (and not i.e. @@"sys_var") form is
// recognized; since we don't know the context, we simply disallow
// double quotes
if (!result.context.prefix.quoted ||
'`' == result.context.prefix.quote) {
for (const auto &item :
find_prefix_ci(m_instance.system_variables,
result.context.prefix.as_identifier)) {
auto name = item->name();
if (result.context.prefix.quote) {
name = shcore::quote_identifier(name);
}
list.emplace_back(std::move(name));
}
}
break;
case Candidate::TABLESPACE:
add_identifiers(m_instance.tablespaces);
break;
case Candidate::USER: {
// construct the prefix which matches the format of user accounts
// stored in cache: 'user'@'host'
std::string prefix;
const auto &as_account = result.context.as_account;
using Account_part = parsers::Sql_completion_result::Account_part;
if (!as_account.user.full.empty()) {
const auto add_part = [&prefix](const Account_part &part) {
prefix += shcore::quote_string(part.unquoted, '\'');
};
add_part(as_account.user);
if (as_account.has_at_sign) {
prefix += '@';
if (!as_account.host.full.empty()) {
add_part(as_account.host);
}
}
// remove the last quote to allow for partial matches
if (prefix.back() == '\'') {
prefix.pop_back();
}
}
// recreate candidate based on quotes used by the user
const auto quote_user = [&as_account](const std::string &name) {
const auto quote_like_part = [](const std::string &s,
const Account_part &part) {
if (part.full.empty()) {
return shcore::quote_string(s, '\'');
} else if (!part.quoted) {
return s;
} else {
if (part.quote == '`') {
return shcore::quote_identifier(s);
} else {
return shcore::quote_string(s, part.quote);
}
}
};
const auto account = shcore::split_account(name);
std::string quoted = quote_like_part(account.user, as_account.user);
quoted += '@';
// if host part was not provided, quote it like the user, to keep
// the same style
quoted += quote_like_part(account.host, as_account.host.full.empty()
? as_account.user
: as_account.host);
return quoted;
};
for (const auto &item : find_prefix_ci(m_instance.users, prefix)) {
list.emplace_back(quote_user(item->name()));
}
} break;
case Candidate::CHARSET:
// character sets can be quoted as strings or identifiers
add_strings_or_identifiers(m_instance.charsets);
break;
case Candidate::COLLATION:
// collations can be quoted as strings or identifiers
add_strings_or_identifiers(m_instance.collations);
break;
case Candidate::PLUGIN:
add_identifiers(m_instance.plugins);
break;
case Candidate::LABEL:
// already filtered
add_identifiers_and_quote_if_needed(result.context.labels);
break;
}
}
return list;
}
void clear_cache() {
m_instance.clear();
set_system_functions(k_current_version);
}
private:
struct Compare_ci : public Case_insensitive_comparator {
using Case_insensitive_comparator::operator();
bool operator()(const Instance::Object &a,
const Instance::Object &b) const {
return (*this)(a.wide_name(), b.wide_name());
}
bool operator()(const Instance::Object &o, const std::wstring &n) const {
return (*this)(o.wide_name(), n);
}
bool operator()(const std::wstring &n, const Instance::Object &o) const {
return (*this)(n, o.wide_name());
}
};
template <class T>
using is_instance_object =
std::enable_if_t<std::is_base_of_v<Instance::Object, T>, int>;
template <class T, is_instance_object<T> = 0>
static void sort(std::vector<T> *container) {
std::sort(container->begin(), container->end(), Compare_ci{});
}
template <class T, is_instance_object<T> = 0>
static T *find(std::vector<T> *container, const std::string &name) {
return const_cast<T *>(find(*container, name));
}
template <class T, is_instance_object<T> = 0>
static const T *find(const std::vector<T> &container,
const std::string &name) {
const auto wname = shcore::utf8_to_wide(name);
const auto range = std::equal_range(container.begin(), container.end(),
wname, Compare_ci{});
for (auto it = range.first; it != range.second; ++it) {
if (wname == it->wide_name()) {
return &(*it);
}
}
return nullptr;
}
template <class T, is_instance_object<T> = 0>
static std::vector<const T *> find_prefix_ci(const std::vector<T> &container,
const std::wstring &prefix) {
auto it = std::lower_bound(container.begin(), container.end(), prefix,
Compare_ci{});
std::vector<const T *> result;
while (it != container.end()) {
if (str_ibeginswith(it->wide_name(), prefix)) {
result.emplace_back(&(*it));
++it;
} else {
break;
}
}
return result;
}
template <class T, is_instance_object<T> = 0>
static std::vector<const T *> find_prefix_ci(const std::vector<T> &container,
const std::string &prefix) {
return find_prefix_ci(container, shcore::utf8_to_wide(prefix));
}
Instance::Objects init_system_functions(base::MySQLVersion version) {
Instance::Objects result;
for (const auto &func :
base::MySQLSymbolInfo::systemFunctionsForVersion(version)) {
result.emplace_back(func + "()");
}
sort(&result);
return result;
}
void refresh_static_symbols(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch_engines(session);
fetch_character_sets(session);
fetch_collations(session);
fetch_system_variables(session);
set_system_functions(session->get_server_version());
}
void refresh_database_symbols(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch_udfs(session);
fetch_logfile_groups(session);
fetch_user_variables(session);
fetch_tablespaces(session);
fetch_users(session);
fetch_plugins(session);
}
template <class T, is_instance_object<T> = 0>
void fetch(const std::shared_ptr<mysqlshdk::db::ISession> &session,
std::string_view query, std::vector<T> *container) const {
if (m_cancelled) {
return;
}
container->clear();
if (const auto result = session->query(query)) {
while (!m_cancelled) {
const auto row = result->fetch_one();
if (!row) {
break;
}
container->emplace_back(row->get_wstring(0));
}
}
if (m_cancelled) {
return;
}
// we're sorting on our own (instead of via query) to be consistent with
// other methods which depend on the order
sort(container);
}
void fetch_schemas(const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch(session, "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA",
&m_instance.schemas);
}
void fetch_tables(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch_tables(session, schema, &schema->tables);
}
void fetch_views(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch_tables(session, schema, &schema->views);
}
void fetch_tables(const std::shared_ptr<mysqlshdk::db::ISession> &session,
const Instance::Schema *schema, Instance::Tables *target) {
fetch(
session,
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE" +
std::string{target == &schema->tables ? "=" : "<>"} +
"'BASE TABLE' AND TABLE_SCHEMA=" + quote_sql_string(schema->name()),
target);
for (auto &table : *target) {
fetch_columns(session, schema, &table);
}
}
void fetch_columns(const std::shared_ptr<mysqlshdk::db::ISession> &session,
const Instance::Schema *schema, Instance::Table *table) {
fetch(session,
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE "
"TABLE_SCHEMA=" +
quote_sql_string(schema->name()) +
" AND TABLE_NAME=" + quote_sql_string(table->name()),
&table->columns);
}
void fetch_functions(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch_routines(session, schema, &schema->functions);
}
void fetch_procedures(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch_routines(session, schema, &schema->procedures);
}
void fetch_routines(const std::shared_ptr<mysqlshdk::db::ISession> &session,
const Instance::Schema *schema,
Instance::Objects *target) {
fetch(session,
"SELECT ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE "
"ROUTINE_TYPE='" +
std::string{target == &schema->functions ? "FUNCTION"
: "PROCEDURE"} +
"' AND ROUTINE_SCHEMA=" + quote_sql_string(schema->name()),
target);
}
void fetch_events(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch(
session,
"SELECT EVENT_NAME FROM INFORMATION_SCHEMA.EVENTS WHERE EVENT_SCHEMA=" +
quote_sql_string(schema->name()),
&schema->events);
}
void fetch_triggers(const std::shared_ptr<mysqlshdk::db::ISession> &session,
Instance::Schema *schema) {
fetch(session,
"SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS WHERE "
"TRIGGER_SCHEMA=" +
quote_sql_string(schema->name()),
&schema->triggers);
}
void fetch_engines(const std::shared_ptr<mysqlshdk::db::ISession> &session) {
// SUPPORT can be YES, DEFAULT, NO, DISABLED
fetch(session,
"SELECT ENGINE FROM INFORMATION_SCHEMA.ENGINES WHERE SUPPORT IN "
"('YES', 'DEFAULT')",
&m_instance.engines);
}
void fetch_character_sets(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch(session,
"SELECT CHARACTER_SET_NAME FROM INFORMATION_SCHEMA.CHARACTER_SETS",
&m_instance.charsets);
}
void fetch_collations(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch(session, "SELECT COLLATION_NAME FROM INFORMATION_SCHEMA.COLLATIONS",
&m_instance.collations);
}
void fetch_system_variables(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
try {
fetch(session,
"SELECT VARIABLE_NAME FROM performance_schema.session_variables",
&m_instance.system_variables);
} catch (const mysqlshdk::db::Error &e) {
// this table does not exist in 5.6
log_warning(
"Failed to fetch system variables for SQL auto-completion: %s",
e.format().c_str());
}
}
void set_system_functions(const Version &version) {
static const auto k_system_functions_57 =
init_system_functions(base::MySQLVersion::MySQL57);
static const auto k_system_functions_80 =
init_system_functions(base::MySQLVersion::MySQL80);
m_instance.system_functions = version < Version(8, 0, 0)
? &k_system_functions_57
: &k_system_functions_80;
}
void fetch_udfs(const std::shared_ptr<mysqlshdk::db::ISession> &session) {
try {
fetch(session,
"SELECT UDF_NAME FROM performance_schema.user_defined_functions",
&m_instance.udfs);
} catch (const mysqlshdk::db::Error &) {
try {
// this table does not exist in 5.7, mysql.func does not provide info on
// functions registered by a component or plugin
fetch(session, "SELECT name FROM mysql.func", &m_instance.udfs);
} catch (const mysqlshdk::db::Error &e) {
// some users don't have access to mysql tables
log_warning("Failed to fetch UDFs for SQL auto-completion: %s",
e.format().c_str());
}
}
}
void fetch_logfile_groups(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch(session,
"SELECT LOGFILE_GROUP_NAME FROM INFORMATION_SCHEMA.FILES WHERE "
"LOGFILE_GROUP_NAME IS NOT NULL",
&m_instance.logfile_groups);
DBUG_EXECUTE_IF("sql_auto_completion_logfile_group", {
m_instance.logfile_groups.emplace_back("log_file_group");
});
}
void fetch_user_variables(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
try {
std::string query =
"SELECT VARIABLE_NAME FROM "
"performance_schema.user_variables_by_thread WHERE THREAD_ID=";
if (session->get_server_version() >= Version(8, 0, 16)) {
query += "PS_CURRENT_THREAD_ID()";
} else {
query += "sys.ps_thread_id(NULL)";
}
fetch(session, query, &m_instance.user_variables);
} catch (const mysqlshdk::db::Error &e) {
// this table does not exist in 5.6
log_warning("Failed to fetch user variables for SQL auto-completion: %s",
e.format().c_str());
}
}
void fetch_tablespaces(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
// tablespaces created by users cannot contain a '/' character and cannot
// begin with 'innodb_'
fetch(session,
"SELECT TABLESPACE_NAME FROM INFORMATION_SCHEMA.FILES WHERE "
"TABLESPACE_NAME NOT LIKE '%/%' AND TABLESPACE_NAME NOT LIKE "
"'innodb\\_%' AND TABLESPACE_NAME<>'mysql'",
&m_instance.tablespaces);
}
void fetch_users(const std::shared_ptr<mysqlshdk::db::ISession> &session) {
try {
fetch(session,
"SELECT CONCAT(QUOTE(User),'@',QUOTE(Host)) FROM mysql.user",
&m_instance.users);
} catch (const mysqlshdk::db::Error &) {
// some users don't have access to the mysql tables
// in some versions of MySQL server the GRANTEE column is truncated,
// append a quote in this case
fetch(
session,
R"(SELECT DISTINCT IF(RIGHT(GRANTEE, 1)="'",GRANTEE,CONCAT(GRANTEE,"'")) FROM INFORMATION_SCHEMA.USER_PRIVILEGES)",
&m_instance.users);
}
}
void fetch_plugins(const std::shared_ptr<mysqlshdk::db::ISession> &session) {
fetch(session, "SELECT PLUGIN_NAME FROM INFORMATION_SCHEMA.PLUGINS",
&m_instance.plugins);
}
Instance m_instance;
volatile bool m_cancelled = false;
};
Provider_sql::Provider_sql()
: m_completion_context{k_current_version},
m_cache(std::make_unique<Cache>()) {
m_completion_context.set_filtered(true);
m_completion_context.set_uppercase_keywords(true);
m_completion_context.set_sql_mode(std::string{k_default_sql_mode_80});
}
Provider_sql::~Provider_sql() = default;
Completion_list Provider_sql::complete_schema(const std::string &prefix) const {
return m_cache->complete_schema(prefix);
}
/**
* Return list of possible completions for the provided text.
*
* @param text full text to be completed
* @param compl_offset offset in text where the completion should start
* on return, assigned to the start position being
* considered for completion
* @return list of completion strings
*
* compl_offset is expected to be last string token in the text. Tokens
* are broken at one of =+-/\\*?\"'`&<>;|@{([])}
*/
Completion_list Provider_sql::complete(const std::string &buffer,
const std::string &line,
size_t *compl_offset) {
// completer needs full query
const auto sql = buffer + line;
// Line contains the line buffer up to the offset where TAB was pressed,
// compl_offset is the offset in that line where linenoise thinks there's a
// break character. Since it does not consider '.' as a break character, we
// just send the whole line up to the TAB, completer will find the prefix and
// we can adjust compl_offset accordingly.
auto result = m_completion_context.complete(sql, sql.length());
if (result.context.prefix.full.length() <= line.length()) {
*compl_offset = line.length() - result.context.prefix.full.length();
} else {
// this should not happen
assert(false);
*compl_offset = 0;
}
return m_cache->complete(std::move(result));
}
void Provider_sql::interrupt_rehash() { m_cache->cancel(); }
void Provider_sql::refresh_schema_cache(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
update_completion_context(session);
m_cache->refresh_schemas(session);
}
void Provider_sql::refresh_name_cache(
const std::shared_ptr<mysqlshdk::db::ISession> &session,
const std::string ¤t_schema, bool force) {
update_completion_context(session);
m_completion_context.set_active_schema(current_schema);
m_cache->refresh_schema(session, current_schema, force);
}
void Provider_sql::update_completion_context(
const std::shared_ptr<mysqlshdk::db::ISession> &session) {
auto server_version = session->get_server_version();
DBUG_EXECUTE_IF("sql_auto_completion_unsupported_version_lower",
{ server_version = Version(1, 2, 3); });
DBUG_EXECUTE_IF("sql_auto_completion_unsupported_version_higher",
{ server_version = Version(10, 9, 8); });
auto version = std::max(server_version, Version(5, 7, 0));
version = std::min(version, k_current_version);
if (version != server_version) {
log_warning(
"Connected to an unsupported server version %s, SQL auto-completion is "
"falling back to: %s",
server_version.get_full().c_str(), version.get_full().c_str());
}
m_completion_context.set_server_version(version);
m_completion_context.set_sql_mode(session->get_sql_mode());
}
void Provider_sql::clear_name_cache() {
reset_completion_context();
m_cache->clear_cache();
}
void Provider_sql::reset_completion_context() {
m_completion_context.set_server_version(k_current_version);
m_completion_context.set_sql_mode(std::string{k_default_sql_mode_80});
m_completion_context.set_active_schema({});
}
} // namespace completer
} // namespace shcore