mysqlshdk/libs/utils/utils_general.h (304 lines of code) (raw):
/*
* Copyright (c) 2014, 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
*/
#ifndef MYSQLSHDK_LIBS_UTILS_UTILS_GENERAL_H_
#define MYSQLSHDK_LIBS_UTILS_UTILS_GENERAL_H_
#include <charconv>
#include <chrono>
#include <functional>
#include <list>
#include <set>
#include <sstream>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <vector>
#ifdef _WIN32
#include <io.h>
#include <windows.h>
#undef DELETE
#undef ERROR
#else
#include <unistd.h>
#endif
#include "mysqlshdk/include/scripting/naming_style.h"
#include "mysqlshdk/include/scripting/types.h"
#include "mysqlshdk/include/shellcore/ishell_core.h"
#include "mysqlshdk/libs/db/connection_options.h"
#include "mysqlshdk/libs/ssh/ssh_connection_options.h"
#include "mysqlshdk/libs/utils/utils_string.h"
namespace shcore {
/**
* Calculate in compile time length/size of an array.
*
* @param array
* @return Return size of an array.
*/
template <class T, std::size_t N>
constexpr static std::size_t array_size(const T (&)[N]) noexcept {
return N;
}
class Scoped_callback {
public:
explicit Scoped_callback(std::function<void()> c) noexcept
: m_callback{std::move(c)} {}
Scoped_callback() = default;
Scoped_callback(const Scoped_callback &) = delete;
Scoped_callback &operator=(const Scoped_callback &) = delete;
Scoped_callback(Scoped_callback &&o) noexcept { *this = std::move(o); }
Scoped_callback &operator=(Scoped_callback &&o) noexcept {
if (this != &o) std::swap(m_callback, o.m_callback);
return *this;
}
~Scoped_callback() noexcept {
try {
call();
} catch (const std::exception &e) {
log_error("Unexpected exception: %s", e.what());
}
}
void call() {
if (!m_callback) return;
std::exchange(m_callback, nullptr)();
}
void cancel() { m_callback = nullptr; }
private:
std::function<void()> m_callback;
};
class Scoped_callback_list {
public:
Scoped_callback_list() = default;
Scoped_callback_list(const Scoped_callback_list &) = delete;
Scoped_callback_list(Scoped_callback_list &&o) noexcept {
*this = std::move(o);
}
Scoped_callback_list &operator=(Scoped_callback_list &&o) noexcept {
if (this != &o) {
std::swap(m_callbacks, o.m_callbacks);
std::swap(m_cancelled, o.m_cancelled);
}
return *this;
}
~Scoped_callback_list() noexcept {
try {
call();
} catch (const std::exception &e) {
log_error("Unexpected exception: %s", e.what());
}
}
void push_back(std::function<void()> c) noexcept {
m_callbacks.push_back(std::move(c));
}
void push_front(std::function<void()> c) noexcept {
m_callbacks.push_front(std::move(c));
}
void call() {
if (m_cancelled) return;
m_cancelled = true; // can only call once (assume cancelled)
try {
for (const auto &cb : m_callbacks) cb();
} catch (const std::exception &e) {
log_error("Unexpected exception: %s", e.what());
throw;
}
m_callbacks.clear();
}
void cancel() {
m_cancelled = true;
m_callbacks.clear();
}
bool empty() const { return m_callbacks.empty(); }
private:
std::list<std::function<void()>> m_callbacks;
bool m_cancelled = false;
};
using on_leave_scope = Scoped_callback;
enum class OperatingSystem {
UNKNOWN,
DEBIAN,
REDHAT,
LINUX,
WINDOWS,
MACOS,
SOLARIS
};
std::string SHCORE_PUBLIC to_string(OperatingSystem os_type);
bool SHCORE_PUBLIC is_valid_identifier(const std::string &name);
mysqlshdk::db::Connection_options SHCORE_PUBLIC
get_connection_options(const std::string &uri, bool set_defaults = true);
mysqlshdk::ssh::Ssh_connection_options SHCORE_PUBLIC
get_ssh_connection_options(const std::string &uri, bool set_defaults = true,
const std::string &config_path = "");
std::string SHCORE_PUBLIC get_system_user();
std::string SHCORE_PUBLIC strip_password(const std::string &connstring);
std::string SHCORE_PUBLIC strip_ssl_args(const std::string &connstring);
char SHCORE_PUBLIC *mysh_get_stdin_password(const char *prompt);
std::vector<std::string> SHCORE_PUBLIC split_string(std::string_view input,
std::string_view separator,
bool compress = false);
std::vector<std::string> SHCORE_PUBLIC
split_string_chars(std::string_view input, std::string_view separator_chars,
bool compress = false);
bool SHCORE_PUBLIC match_glob(const std::string_view pattern,
const std::string_view s,
bool case_sensitive = false);
std::string SHCORE_PUBLIC to_camel_case(const std::string &name);
std::string SHCORE_PUBLIC from_camel_case(const std::string &name);
std::string SHCORE_PUBLIC from_camel_case_to_dashes(const std::string &name);
std::string SHCORE_PUBLIC errno_to_string(int err);
struct Account {
enum class Auto_quote {
/**
* No auto-quotes, string must be a valid account name.
*/
NO,
/**
* Host will be auto-quoted, multiple unqouted '@' characters are NOT
* allowed.
*/
HOST,
/**
* String is a result of i.e. CURRENT_USER() function and is not quoted at
* all. Host will be auto-quoted, multiple unqouted '@' characters are
* allowed, the last one marks the beginning of the host name.
*/
USER_AND_HOST,
};
std::string user;
std::string host;
bool operator<(const Account &a) const {
return std::tie(user, host) < std::tie(a.user, a.host);
}
bool operator==(const Account &a) const {
return user == a.user && host == a.host;
}
};
void SHCORE_PUBLIC split_account(
const std::string &account, std::string *out_user, std::string *out_host,
Account::Auto_quote auto_quote = Account::Auto_quote::NO);
Account SHCORE_PUBLIC
split_account(const std::string &account,
Account::Auto_quote auto_quote = Account::Auto_quote::NO);
template <typename C>
std::vector<Account> to_accounts(
const C &c, Account::Auto_quote auto_quote = Account::Auto_quote::NO) {
std::vector<Account> result;
for (const auto &i : c) {
result.emplace_back(split_account(i, auto_quote));
}
return result;
}
std::string SHCORE_PUBLIC make_account(const std::string &user,
const std::string &host);
std::string SHCORE_PUBLIC make_account(const Account &account);
std::string SHCORE_PUBLIC get_member_name(std::string_view name,
shcore::NamingStyle style);
/**
* Ensures at most 2 identifiers are found on the string:
* - if 2 identifiers are found then they are set to schema and table
* - If 1 identifier is found it is set to table
* Ensures the table name is not empty.
*/
void SHCORE_PUBLIC split_schema_and_table(const std::string &str,
std::string *out_schema,
std::string *out_table,
bool allow_ansi_quotes = false);
/**
* Ensures at most 3 identifiers are found on the string:
* - if 3 identifiers are found then they are set to schema, table and object
* - if 2 identifiers are found then they are set to table and object
* - if 1 identifier is found it is set to object
* Ensures the object name is not empty.
*/
void SHCORE_PUBLIC split_schema_table_and_object(
const std::string &str, std::string *out_schema, std::string *out_table,
std::string *out_object, bool allow_ansi_quotes = false);
void SHCORE_PUBLIC split_priv_level(const std::string &str,
std::string *out_schema,
std::string *out_object,
size_t *out_leftover = nullptr);
std::string SHCORE_PUBLIC unquote_identifier(const std::string &str,
bool allow_ansi_quotes = false);
std::string SHCORE_PUBLIC unquote_sql_string(const std::string &str);
/** Substitute variables in string.
*
* str_subvar("hello ${foo}",
* [](const std::string&) { return "world"; },
* "${", "}");
* --> "hello world";
*
* str_subvar("hello $foo!",
* [](const std::string&) { return "world"; },
* "$", "");
* --> "hello world!";
*
* If var_end is "", then the variable name will span until the
*/
std::string SHCORE_PUBLIC str_subvars(
std::string_view s,
const std::function<std::string(std::string_view)> &subvar =
[](std::string_view var) {
return shcore::get_member_name(var, shcore::current_naming_style());
},
std::string_view var_begin = "<<<", std::string_view var_end = ">>>");
void SHCORE_PUBLIC sleep_ms(uint32_t ms);
void SHCORE_PUBLIC sleep(std::chrono::milliseconds duration);
OperatingSystem SHCORE_PUBLIC get_os_type();
/**
* Provides host CPU type, i.e. aarch64, x86_64, etc.
*
* @return machine type
*/
std::string SHCORE_PUBLIC get_machine_type();
/**
* Provides long version of mysqlsh, including version number, OS type, MySQL
* version number and build type.
*
* @return version string
*/
const char *SHCORE_PUBLIC get_long_version();
#ifdef _WIN32
std::string SHCORE_PUBLIC last_error_to_string(DWORD code);
#endif // _WIN32
template <class T>
auto lexical_cast(T &&data) noexcept {
return std::forward<T>(data);
}
template <class T>
auto lexical_cast(std::string_view str) {
// this version converts from string to something else
// same behavior as boost: doesn't make sense so convert back to these types
static_assert(!std::is_same_v<std::remove_cv_t<T>, char *> &&
!std::is_same_v<std::remove_cv_t<T>, char> &&
!std::is_same_v<std::remove_cv_t<T>, std::string_view>,
"Invalid type to convert to.");
if constexpr (std::is_same_v<T, std::string>) {
// simple copies are allowed
return T{str};
} else if constexpr (std::is_same<T, bool>::value) {
// conversion to bool
if (shcore::str_caseeq(str, "true")) return true;
if (shcore::str_caseeq(str, "false")) return false;
std::stringstream ss;
ss << str;
if (std::is_unsigned<T>::value && ss.peek() == '-')
throw std::invalid_argument("Unable to perform conversion.");
T t;
ss >> t;
if (ss.fail()) throw std::invalid_argument("Unable to perform conversion.");
if (!ss.eof())
throw std::invalid_argument("Conversion did not consume whole input.");
return t;
} else if constexpr (std::is_integral_v<T>) {
// some compilers don't have FP implementations of std::from_chars (GCC and
// Clang) so it can only be used with integrals
T value;
auto [ptr, ec] =
std::from_chars(str.data(), str.data() + str.size(), value);
if (ec != std::errc())
throw std::invalid_argument("Unable to perform conversion.");
if (ptr != (str.data() + str.size()))
throw std::invalid_argument("Conversion did not consume whole input.");
return value;
} else {
// all other cases
std::stringstream ss;
ss << str;
if (std::is_unsigned<T>::value && ss.peek() == '-')
throw std::invalid_argument("Unable to perform conversion.");
T t;
ss >> t;
if (ss.fail()) throw std::invalid_argument("Unable to perform conversion.");
if (!ss.eof())
throw std::invalid_argument("Conversion did not consume whole input.");
return t;
}
}
template <class T, class S>
auto lexical_cast(S &&data) {
// lexical_cast doesn't allow to convert to "char*" or std::string_view
static_assert(!std::is_same_v<T, char *> &&
!std::is_same_v<T, const char *> &&
!std::is_same_v<T, std::string_view>);
// lexical_cast should be to / from strings, so at least T or S must be one
static_assert(std::is_same_v<std::decay_t<S>, char *> ||
std::is_same_v<std::decay_t<S>, const char *> ||
std::is_same_v<std::decay_t<S>, std::string> ||
std::is_same_v<T, std::string>);
/*
- if T and S are the same, forward S
- if S is a string, use the specialization for strings (above this one)
- if T is a string, convert it here
- every other combination is "translated" through a std::stringstream
*/
if constexpr (std::is_same_v<std::decay_t<S>, T>) {
// if T and S match, just return the parameter
return std::forward<T>(data);
} else if constexpr (std::is_same_v<std::decay_t<S>, char *> ||
std::is_same_v<std::decay_t<S>, const char *> ||
std::is_same_v<std::decay_t<S>, std::string>) {
// if S is char* or std::string (const or non-const) then use
// std::string_view version
return lexical_cast<T>(std::string_view{data});
} else {
// convert from S (which is not char*, std::string or std::string_view) to
// std::string
if constexpr (std::is_same_v<std::decay_t<S>, bool>) {
return std::string{data ? "true" : "false"};
} else if constexpr (std::is_integral_v<std::decay_t<S>>) {
std::array<char, 64> str;
auto [ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), data);
if (ec == std::errc()) return std::string{str.data(), ptr};
throw std::invalid_argument("Unable to perform conversion.");
} else {
std::stringstream ss;
ss << data;
return ss.str();
}
}
}
template <class T, class S>
auto lexical_cast(S &&data, T default_value) noexcept {
try {
return lexical_cast<T>(std::forward<S>(data));
} catch (...) {
}
return default_value;
}
/**
* Wrapper for the std::getline() function which removes the last character
* if it's carriage return.
*/
std::istream &getline(std::istream &in, std::string &out);
/**
* Verifies the status code of an application.
*
* @param status - status code to be checked
* @param error - if execution was not successful, contains details on
* corresponding error
*
* @returns true if status code corresponds to a successful execution of an
* application.
*/
bool verify_status_code(int status, std::string *error);
/**
* Sets the environment variable 'name' to the value of 'value'.
* If value is null or empty, variable is removed instead.
*
* @param name - name of the environment variable to set
* @param value - value of the environment variable
*
* @returns true if the environment variable was set successfully.
*/
bool setenv(const char *name, const char *value);
bool setenv(const char *name, const std::string &value);
bool setenv(const std::string &name, const std::string &value);
/**
* Sets the environment variable, string must be in form: name=value.
* If value is empty, variable is removed instead.
*
* @param name_value - name and the new value of the environment variable
*
* @returns true if the environment variable was set successfully.
*/
bool setenv(const std::string &name_value);
/**
* Clears the environment variable called 'name'.
*
* @param name - name of the environment variable to clear
*
* @returns true if the environment variable was cleared successfully.
*/
bool unsetenv(const char *name);
bool unsetenv(const std::string &name);
#ifdef _WIN32
#define STDIN_FILENO _fileno(stdin)
#define STDOUT_FILENO _fileno(stdout)
#define isatty _isatty
#endif
} // namespace shcore
#endif // MYSQLSHDK_LIBS_UTILS_UTILS_GENERAL_H_