/*
Copyright (c) 2000, 2025, 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
*/

// mysql command tool

#include "my_config.h"

#include <fcntl.h>
#include <sys/types.h>
#include <cerrno>
#include <cinttypes>
#include <cmath>
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <ctime>

#include "client/client_query_attributes.h"
#include "client/include/client_priv.h"
#include "client/include/user_registration.h"
#include "client/multi_option.h"
#include "client/my_readline.h"
#include "client/pattern_matcher.h"
#include "compression.h"
#include "lex_string.h"
#include "m_string.h"
#include "my_compiler.h"
#include "my_dbug.h"
#include "my_default.h"
#include "my_dir.h"
#include "my_inttypes.h"
#include "my_io.h"
#include "my_macros.h"
#include "my_rdtsc.h"
#include "mysql/my_loglevel.h"
#include "mysql/plugin_client_telemetry.h"
#include "mysql/strings/int2str.h"
#include "mysql/strings/m_ctype.h"
#include "nulls.h"
#include "str2int.h"
#include "strcont.h"
#include "string_with_len.h"
#include "strmake.h"
#include "strxmov.h"
#include "strxnmov.h"
#include "template_utils.h"
#include "typelib.h"
#include "violite.h"

#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif

#if defined(USE_LIBEDIT_INTERFACE)
#include <clocale>
#endif

#ifdef HAVE_PWD_H
#include <pwd.h>
#endif

#if defined(HAVE_TERM_H)
#define NOMACROS  // move() macro interferes with std::move.
#include <curses.h>
#include <term.h>
#endif

#if defined(_WIN32)
#include <conio.h>

// Not using syslog but EventLog on Win32, so a dummy facility is enough.
#define LOG_USER 0
#else
#include <readline.h>
#include <syslog.h>

#define HAVE_READLINE
#define USE_POPEN
#endif

#include <mysqld_error.h>
#include <algorithm>
#include <new>

#include "sql-common/net_ns.h"
#include "sql_common.h"

using std::max;
using std::min;

const char *VER = "14.14";

/* Don't try to make a nice table if the data is too big */
#define MAX_COLUMN_LENGTH 1024

/* Buffer to hold 'version' and 'version_comment' */
static char *server_version = nullptr;

/* Array of options to pass to libemysqld */
#define MAX_SERVER_ARGS 64

/* Maximum memory limit that can be claimed by alloca(). */
#define MAX_ALLOCA_SIZE 512

#include "sql_string.h"

#ifdef FN_NO_CASE_SENSE
#define cmp_database(cs, A, B) my_strcasecmp((cs), (A), (B))
#else
#define cmp_database(cs, A, B) strcmp((A), (B))
#endif

#include "client/completion_hash.h"
#include "print_version.h"
#include "welcome_copyright_notice.h"  // ORACLE_WELCOME_COPYRIGHT_NOTICE

#define PROMPT_CHAR '\\'
#define DEFAULT_DELIMITER ";"

#define MAX_BATCH_BUFFER_SIZE (1024L * 1024L * 1024L)

client_query_attributes *telemetry_client_attrs = nullptr;

/** default set of patterns used for history exclusion filter */
const static std::string HI_DEFAULTS(
    "*IDENTIFIED*:*PASSWORD*:*https?*/p/?*/n/?*/b/?*/o/*:*ocid1.stream*");

/** used for matching which history lines to ignore */
static Pattern_matcher ignore_matcher;

struct STATUS {
  int exit_status;
  ulong query_start_line;
  char *file_name;
  LINE_BUFFER *line_buff;
  bool batch, add_to_history;
};

static HashTable ht;
static MEM_ROOT argv_alloc{PSI_NOT_INSTRUMENTED, 512};

enum enum_info_type { INFO_INFO, INFO_ERROR, INFO_RESULT };
typedef enum enum_info_type INFO_TYPE;

static MYSQL mysql_handle; /* The connection */
static bool ignore_errors = false, wait_flag = false, quick = false,
            connected = false, opt_raw_data = false, unbuffered = false,
            output_tables = false, opt_rehash = true, skip_updates = false,
            safe_updates = false, one_database = false, opt_compress = false,
            using_opt_local_infile = false, vertical = false,
            line_numbers = true, column_names = true, opt_html = false,
            opt_xml = false, opt_nopager = true, opt_outfile = false,
            named_cmds = false, opt_nobeep = false, opt_reconnect = true,
            default_pager_set = false, opt_sigint_ignore = false,
            auto_vertical_output = false, show_warnings = false,
            executing_query = false, interrupted_query = false,
            ignore_spaces = false, sigint_received = false, opt_syslog = false,
            opt_binhex = false;
static bool opt_binary_as_hex_set_explicitly = false;
static bool debug_info_flag, debug_check_flag;
static bool column_types_flag;
static bool preserve_comments = true;
static ulong opt_max_allowed_packet, opt_net_buffer_length;
static uint verbose = 0, opt_silent = 0, opt_mysql_port = 0,
            opt_local_infile = 0;
static uint opt_enable_cleartext_plugin = 0;
static bool using_opt_enable_cleartext_plugin = false;
static uint my_end_arg;
static char *opt_mysql_unix_port = nullptr;
static char *opt_bind_addr = nullptr;
static int connect_flag = CLIENT_INTERACTIVE;
static bool opt_binary_mode = false;
static bool opt_commands = false;
static bool opt_connect_expired_password = false;
static char *current_host;
static char *dns_srv_name;
static char *current_db;
static char *current_user = nullptr;
static char *current_prompt = nullptr;
static char *delimiter_str = nullptr;
static const char *default_charset = MYSQL_AUTODETECT_CHARSET_NAME;
#ifdef HAVE_READLINE
static char *histfile;
static char *histfile_tmp;
#endif
static char *opt_histignore = nullptr;
static String glob_buffer, old_buffer;
static String processed_prompt;
static String dollar_quote;
static bool dollar_quote_supported = false;
static char *full_username = nullptr, *part_username = nullptr,
            *default_prompt = nullptr;
static char *current_os_user = nullptr, *current_os_sudouser = nullptr;
static int wait_time = 5;
static STATUS status;
static ulong select_limit, max_join_size, opt_connect_timeout = 0;
static char mysql_charsets_dir[FN_REFLEN + 1];
static char *opt_plugin_dir = nullptr, *opt_default_auth = nullptr;
static char *opt_load_data_local_dir = nullptr;
#ifdef HAVE_SETNS
static char *opt_network_namespace = nullptr;
#endif
static const char *xmlmeta[] = {
    "&", "&amp;", "<", "&lt;", ">", "&gt;", "\"", "&quot;",
    /* Turn \0 into a space. Why not &#0;? That's not valid XML or HTML. */
    "\0", " ", nullptr, nullptr};
static const char *day_names[] = {"Sun", "Mon", "Tue", "Wed",
                                  "Thu", "Fri", "Sat"};
static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static char default_pager[FN_REFLEN];
static char pager[FN_REFLEN], outfile[FN_REFLEN];
static FILE *PAGER, *OUTFILE;
static MEM_ROOT hash_mem_root(PSI_NOT_INSTRUMENTED, 16384);
static uint prompt_counter;
static char delimiter[16] = DEFAULT_DELIMITER;
static size_t delimiter_length = 1;
unsigned short terminal_width = 80;
static uint opt_zstd_compress_level = default_zstd_compression_level;
static char *opt_compress_algorithm = nullptr;

#if defined(_WIN32)
static char *shared_memory_base_name = nullptr;
#endif
static uint opt_protocol = 0;
static const CHARSET_INFO *charset_info = &my_charset_latin1;

static char *opt_oci_config_file = nullptr;
static char *opt_authentication_oci_client_config_profile = nullptr;
static char *opt_authentication_openid_connect_client_id_token_file = nullptr;
static char *opt_register_factor = nullptr;

static bool opt_tel_plugin = false;
static const char *opt_tel_plugin_name = "telemetry_client";

static bool opt_system_command = false;

static struct my_option my_empty_options[] = {
    {nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
     0, nullptr, 0, nullptr}};

static void usage(int version);

#include "client/include/authentication_kerberos_clientopt-vars.h"
#include "client/include/authentication_webauthn_clientopt-vars.h"
#include "client/include/caching_sha2_passwordopt-vars.h"
#include "client/include/multi_factor_passwordopt-vars.h"
#include "client/include/sslopt-vars.h"

const char *default_dbug_option = "d:t:o,/tmp/mysql.trace";
static void *ssl_session_data = nullptr;

static Multi_option opt_init_commands;

/*
  completion_hash is an auxiliary feature for mysql client to complete
  an object name(db name, table name and field name) automatically.
  e.g.
  mysql> use my_d
  then press <TAB>, it will check the hash and complete the db name
  for users.
  the result will be:
  mysql> use my_dbname

  In general, this feature is only on when it is an interactive mysql client.
  It is not possible to use it in test case.

  For using this feature in test case, we add the option in debug code.
*/
#ifndef NDEBUG
static bool opt_build_completion_hash = false;
#endif

#ifdef _WIN32
/*
  A flag that indicates if --execute buffer has already been converted,
  to avoid double conversion on reconnect.
*/
static bool execute_buffer_conversion_done{false};

/*
  my_win_is_console(...) is quite slow.
  We cache my_win_is_console() results for stdout and stderr.
  Any other output files, except stdout and stderr,
  cannot be Windows console.
  Note, if mysql.exe is executed from a service, its _fileno(stdout) is -1,
  so shift (1 << -1) can return implementation defined result.
  This corner case is taken into account, as the shift result
  will be multiplied to 0 and we'll get 0 as a result.
  The same is true for stderr.
*/
static uint win_is_console_cache =
    ((my_win_is_console(stdout)) * (1 << _fileno(stdout))) |
    ((my_win_is_console(stderr)) * (1 << _fileno(stderr)));

static inline bool my_win_is_console_cached(FILE *file) {
  return win_is_console_cache & (1 << _fileno(file));
}
#endif /* _WIN32 */

/* Various printing flags */
#define MY_PRINT_ESC_0 1 /* Replace 0x00 bytes to "\0"              */
#define MY_PRINT_SPS_0 2 /* Replace 0x00 bytes to space             */
#define MY_PRINT_XML 4   /* Encode XML entities                     */
#define MY_PRINT_MB 8    /* Recognize multi-byte characters         */
#define MY_PRINT_CTRL 16 /* Replace TAB, NL, CR to "\t", "\n", "\r" */

void tee_write(FILE *file, const char *s, size_t slen, int flags);
void tee_fprintf(FILE *file, const char *fmt, ...)
    MY_ATTRIBUTE((format(printf, 2, 3)));
void tee_fputs(const char *s, FILE *file);
void tee_puts(const char *s, FILE *file);
void tee_putc(int c, FILE *file);
static void tee_print_sized_data(const char *, unsigned int, unsigned int,
                                 bool);
/* The names of functions that actually do the manipulation. */
static int get_options(int *argc_ptr, char ***argv_ptr);
extern "C" bool get_one_option(int optid, const struct my_option *opt,
                               char *argument);
static int com_quit(String *buffer, char *), com_go(String *buffer, char *),
    com_ego(String *buffer, char *), com_print(String *buffer, char *),
    com_help(String *buffer, char *), com_clear(String *buffer, char *),
    com_connect(String *buffer, char *), com_status(String *buffer, char *),
    com_use(String *buffer, char *), com_source(String *buffer, char *),
    com_rehash(String *buffer, char *), com_tee(String *buffer, char *),
    com_notee(String *buffer, char *), com_charset(String *buffer, char *),
    com_prompt(String *buffer, char *), com_delimiter(String *buffer, char *),
    com_warnings(String *buffer, char *),
    com_nowarnings(String *buffer, char *),
    com_resetconnection(String *buffer, char *),
    com_query_attributes(String *buffer, char *),
    com_ssl_session_data_print(String *buffer, char *);
static int com_shell(String *buffer, char *);

#ifdef USE_POPEN
static int com_nopager(String *buffer, char *),
    com_pager(String *buffer, char *), com_edit(String *buffer, char *);
#endif

static int read_and_execute(bool interactive);
static bool init_connection_options(MYSQL *mysql);
static int sql_connect(char *host, char *database, char *user, uint silent);
static const char *server_version_string(MYSQL *con);
static int put_info(const char *str, INFO_TYPE info, uint error = 0,
                    const char *sqlstate = nullptr);
static int put_error(MYSQL *con);
static void put_error_if_any(MYSQL *con);
static void safe_put_field(const char *pos, ulong length);
static void xmlencode_print(const char *src, uint length);
static void init_pager();
static void end_pager();
static void init_tee(const char *);
static void end_tee();
static const char *construct_prompt();
static inline void reset_prompt(char *in_string, bool *ml_comment);
static char *get_arg(char *line, bool get_next_arg);
static void init_username();
static void add_int_to_prompt(int toadd);
static int get_result_width(MYSQL_RES *res);
static int get_field_disp_length(MYSQL_FIELD *field);
static int normalize_dbname(const char *line, char *buff, uint buff_size);
static int get_quote_count(const char *line);

static void add_filtered_history(const char *string);
static void add_syslog(const char *line); /* for syslog */
static void fix_line(String *final_command);

static void get_current_os_user();
static void get_current_os_sudouser();

static char *skip_over_comments_and_space(char *ptr, size_t length);
static void trim_leading_comments_and_space(String *buffer,
                                            const char *skip_comments_start);

/* A structure which contains information on the commands this program
   can understand. */

typedef struct {
  const char *name;                 /* User printable name of the function. */
  char cmd_char;                    /* mysql command character. NULL if none */
  int (*func)(String *str, char *); /* Function to call to do the job. */
  bool takes_params;                /* Max parameters for command */
  const char *doc;                  /* Documentation for this function.  */
} COMMANDS;

static COMMANDS commands[] = {
    {"?", '?', com_help, true, "Synonym for `help'."},
    {"clear", 'c', com_clear, false, "Clear the current input statement."},
    {"connect", 'r', com_connect, true,
     "Reconnect to the server. Optional arguments are db and host."},
    {"delimiter", 'd', com_delimiter, true, "Set statement delimiter."},
#ifdef USE_POPEN
    {"edit", 'e', com_edit, false, "Edit command with $EDITOR."},
#endif
    {"ego", 'G', com_ego, false,
     "Send command to mysql server, display result vertically."},
    {"exit", 'q', com_quit, false, "Exit mysql. Same as quit."},
    {"go", 'g', com_go, false, "Send command to mysql server."},
    {"help", 'h', com_help, true, "Display this help."},
#ifdef USE_POPEN
    {"nopager", 'n', com_nopager, false, "Disable pager, print to stdout."},
#endif
    {"notee", 't', com_notee, false, "Don't write into outfile."},
#ifdef USE_POPEN
    {"pager", 'P', com_pager, true,
     "Set PAGER [to_pager]. Print the query results via PAGER."},
#endif
    {"print", 'p', com_print, false, "Print current command."},
    {"prompt", 'R', com_prompt, true, "Change your mysql prompt."},
    {"quit", 'q', com_quit, false, "Quit mysql."},
    {"rehash", '#', com_rehash, false, "Rebuild completion hash."},
    {"source", '.', com_source, true,
     "Execute an SQL script file. Takes a file name as an argument."},
    {"status", 's', com_status, false,
     "Get status information from the server."},
    {"system", '!', com_shell, true,
     "Execute a system shell command, if enabled"},
    {"tee", 'T', com_tee, true,
     "Set outfile [to_outfile]. Append everything into given outfile."},
    {"use", 'u', com_use, true,
     "Use another database. Takes database name as argument."},
    {"charset", 'C', com_charset, true,
     "Switch to another charset. Might be needed for processing binlog with "
     "multi-byte charsets."},
    {"warnings", 'W', com_warnings, false,
     "Show warnings after every statement."},
    {"nowarning", 'w', com_nowarnings, false,
     "Don't show warnings after every statement."},
    {"resetconnection", 'x', com_resetconnection, false,
     "Clean session context."},
    {"query_attributes", 0, com_query_attributes, true,
     "Sets string parameters (name1 value1 name2 value2 ...) for the next "
     "query to pick up."},
    {"ssl_session_data_print", 0, com_ssl_session_data_print, true,
     "Serializes the current SSL session data to stdout or file"},
    /* Get bash-like expansion for some commands */
    {"create table", 0, nullptr, false, ""},
    {"create database", 0, nullptr, false, ""},
    {"show databases", 0, nullptr, false, ""},
    {"show fields from", 0, nullptr, false, ""},
    {"show keys from", 0, nullptr, false, ""},
    {"show tables", 0, nullptr, false, ""},
    {"load data from", 0, nullptr, false, ""},
    {"alter table", 0, nullptr, false, ""},
    {"set option", 0, nullptr, false, ""},
    {"lock tables", 0, nullptr, false, ""},
    {"unlock tables", 0, nullptr, false, ""},
    /* generated 2006-12-28.  Refresh occasionally from lexer. */
    {"ACTION", 0, nullptr, false, ""},
    {"ADD", 0, nullptr, false, ""},
    {"AFTER", 0, nullptr, false, ""},
    {"AGAINST", 0, nullptr, false, ""},
    {"AGGREGATE", 0, nullptr, false, ""},
    {"ALL", 0, nullptr, false, ""},
    {"ALGORITHM", 0, nullptr, false, ""},
    {"ALTER", 0, nullptr, false, ""},
    {"ANALYZE", 0, nullptr, false, ""},
    {"AND", 0, nullptr, false, ""},
    {"ANY", 0, nullptr, false, ""},
    {"AS", 0, nullptr, false, ""},
    {"ASC", 0, nullptr, false, ""},
    {"ASCII", 0, nullptr, false, ""},
    {"ASENSITIVE", 0, nullptr, false, ""},
    {"AUTO_INCREMENT", 0, nullptr, false, ""},
    {"AVG", 0, nullptr, false, ""},
    {"AVG_ROW_LENGTH", 0, nullptr, false, ""},
    {"BACKUP", 0, nullptr, false, ""},
    {"BDB", 0, nullptr, false, ""},
    {"BEFORE", 0, nullptr, false, ""},
    {"BEGIN", 0, nullptr, false, ""},
    {"BERKELEYDB", 0, nullptr, false, ""},
    {"BETWEEN", 0, nullptr, false, ""},
    {"BIGINT", 0, nullptr, false, ""},
    {"BINARY", 0, nullptr, false, ""},
    {"BINLOG", 0, nullptr, false, ""},
    {"BIT", 0, nullptr, false, ""},
    {"BLOB", 0, nullptr, false, ""},
    {"BOOL", 0, nullptr, false, ""},
    {"BOOLEAN", 0, nullptr, false, ""},
    {"BOTH", 0, nullptr, false, ""},
    {"BTREE", 0, nullptr, false, ""},
    {"BY", 0, nullptr, false, ""},
    {"BYTE", 0, nullptr, false, ""},
    {"CACHE", 0, nullptr, false, ""},
    {"CALL", 0, nullptr, false, ""},
    {"CASCADE", 0, nullptr, false, ""},
    {"CASCADED", 0, nullptr, false, ""},
    {"CASE", 0, nullptr, false, ""},
    {"CHAIN", 0, nullptr, false, ""},
    {"CHANGE", 0, nullptr, false, ""},
    {"CHANGED", 0, nullptr, false, ""},
    {"CHAR", 0, nullptr, false, ""},
    {"CHARACTER", 0, nullptr, false, ""},
    {"CHARSET", 0, nullptr, false, ""},
    {"CHECK", 0, nullptr, false, ""},
    {"CHECKSUM", 0, nullptr, false, ""},
    {"CIPHER", 0, nullptr, false, ""},
    {"CLIENT", 0, nullptr, false, ""},
    {"CLOSE", 0, nullptr, false, ""},
    {"CODE", 0, nullptr, false, ""},
    {"COLLATE", 0, nullptr, false, ""},
    {"COLLATION", 0, nullptr, false, ""},
    {"COLUMN", 0, nullptr, false, ""},
    {"COLUMNS", 0, nullptr, false, ""},
    {"COMMENT", 0, nullptr, false, ""},
    {"COMMIT", 0, nullptr, false, ""},
    {"COMMITTED", 0, nullptr, false, ""},
    {"COMPACT", 0, nullptr, false, ""},
    {"COMPRESSED", 0, nullptr, false, ""},
    {"CONCURRENT", 0, nullptr, false, ""},
    {"CONDITION", 0, nullptr, false, ""},
    {"CONNECTION", 0, nullptr, false, ""},
    {"CONSISTENT", 0, nullptr, false, ""},
    {"CONSTRAINT", 0, nullptr, false, ""},
    {"CONTAINS", 0, nullptr, false, ""},
    {"CONTINUE", 0, nullptr, false, ""},
    {"CONVERT", 0, nullptr, false, ""},
    {"CREATE", 0, nullptr, false, ""},
    {"CROSS", 0, nullptr, false, ""},
    {"CUBE", 0, nullptr, false, ""},
    {"CURRENT_DATE", 0, nullptr, false, ""},
    {"CURRENT_TIME", 0, nullptr, false, ""},
    {"CURRENT_TIMESTAMP", 0, nullptr, false, ""},
    {"CURRENT_USER", 0, nullptr, false, ""},
    {"CURSOR", 0, nullptr, false, ""},
    {"DATA", 0, nullptr, false, ""},
    {"DATABASE", 0, nullptr, false, ""},
    {"DATABASES", 0, nullptr, false, ""},
    {"DATE", 0, nullptr, false, ""},
    {"DATETIME", 0, nullptr, false, ""},
    {"DAY", 0, nullptr, false, ""},
    {"DAY_HOUR", 0, nullptr, false, ""},
    {"DAY_MICROSECOND", 0, nullptr, false, ""},
    {"DAY_MINUTE", 0, nullptr, false, ""},
    {"DAY_SECOND", 0, nullptr, false, ""},
    {"DEALLOCATE", 0, nullptr, false, ""},
    {"DEC", 0, nullptr, false, ""},
    {"DECIMAL", 0, nullptr, false, ""},
    {"DECLARE", 0, nullptr, false, ""},
    {"DEFAULT", 0, nullptr, false, ""},
    {"DEFINER", 0, nullptr, false, ""},
    {"DELAYED", 0, nullptr, false, ""},
    {"DELAY_KEY_WRITE", 0, nullptr, false, ""},
    {"DELETE", 0, nullptr, false, ""},
    {"DESC", 0, nullptr, false, ""},
    {"DESCRIBE", 0, nullptr, false, ""},
    {"DETERMINISTIC", 0, nullptr, false, ""},
    {"DIRECTORY", 0, nullptr, false, ""},
    {"DISABLE", 0, nullptr, false, ""},
    {"DISCARD", 0, nullptr, false, ""},
    {"DISTINCT", 0, nullptr, false, ""},
    {"DISTINCTROW", 0, nullptr, false, ""},
    {"DIV", 0, nullptr, false, ""},
    {"DO", 0, nullptr, false, ""},
    {"DOUBLE", 0, nullptr, false, ""},
    {"DROP", 0, nullptr, false, ""},
    {"DUAL", 0, nullptr, false, ""},
    {"DUMPFILE", 0, nullptr, false, ""},
    {"DUPLICATE", 0, nullptr, false, ""},
    {"DYNAMIC", 0, nullptr, false, ""},
    {"EACH", 0, nullptr, false, ""},
    {"ELSE", 0, nullptr, false, ""},
    {"ELSEIF", 0, nullptr, false, ""},
    {"ENABLE", 0, nullptr, false, ""},
    {"ENCLOSED", 0, nullptr, false, ""},
    {"END", 0, nullptr, false, ""},
    {"ENGINE", 0, nullptr, false, ""},
    {"ENGINES", 0, nullptr, false, ""},
    {"ENUM", 0, nullptr, false, ""},
    {"ERRORS", 0, nullptr, false, ""},
    {"ESCAPE", 0, nullptr, false, ""},
    {"ESCAPED", 0, nullptr, false, ""},
    {"EVENTS", 0, nullptr, false, ""},
    {"EXECUTE", 0, nullptr, false, ""},
    {"EXISTS", 0, nullptr, false, ""},
    {"EXIT", 0, nullptr, false, ""},
    {"EXPANSION", 0, nullptr, false, ""},
    {"EXPLAIN", 0, nullptr, false, ""},
    {"EXTENDED", 0, nullptr, false, ""},
    {"FALSE", 0, nullptr, false, ""},
    {"FAST", 0, nullptr, false, ""},
    {"FETCH", 0, nullptr, false, ""},
    {"FIELDS", 0, nullptr, false, ""},
    {"FILE", 0, nullptr, false, ""},
    {"FIRST", 0, nullptr, false, ""},
    {"FIXED", 0, nullptr, false, ""},
    {"FLOAT", 0, nullptr, false, ""},
    {"FLOAT4", 0, nullptr, false, ""},
    {"FLOAT8", 0, nullptr, false, ""},
    {"FLUSH", 0, nullptr, false, ""},
    {"FOR", 0, nullptr, false, ""},
    {"FORCE", 0, nullptr, false, ""},
    {"FOREIGN", 0, nullptr, false, ""},
    {"FOUND", 0, nullptr, false, ""},
    {"FROM", 0, nullptr, false, ""},
    {"FULL", 0, nullptr, false, ""},
    {"FULLTEXT", 0, nullptr, false, ""},
    {"FUNCTION", 0, nullptr, false, ""},
    {"GEOMETRY", 0, nullptr, false, ""},
    {"GEOMETRYCOLLECTION", 0, nullptr, false, ""},
    {"GET_FORMAT", 0, nullptr, false, ""},
    {"GLOBAL", 0, nullptr, false, ""},
    {"GRANT", 0, nullptr, false, ""},
    {"GRANTS", 0, nullptr, false, ""},
    {"GROUP", 0, nullptr, false, ""},
    {"HANDLER", 0, nullptr, false, ""},
    {"HASH", 0, nullptr, false, ""},
    {"HAVING", 0, nullptr, false, ""},
    {"HELP", 0, nullptr, false, ""},
    {"HIGH_PRIORITY", 0, nullptr, false, ""},
    {"HOSTS", 0, nullptr, false, ""},
    {"HOUR", 0, nullptr, false, ""},
    {"HOUR_MICROSECOND", 0, nullptr, false, ""},
    {"HOUR_MINUTE", 0, nullptr, false, ""},
    {"HOUR_SECOND", 0, nullptr, false, ""},
    {"IDENTIFIED", 0, nullptr, false, ""},
    {"IF", 0, nullptr, false, ""},
    {"IGNORE", 0, nullptr, false, ""},
    {"IMPORT", 0, nullptr, false, ""},
    {"IN", 0, nullptr, false, ""},
    {"INDEX", 0, nullptr, false, ""},
    {"INDEXES", 0, nullptr, false, ""},
    {"INFILE", 0, nullptr, false, ""},
    {"INNER", 0, nullptr, false, ""},
    {"INNOBASE", 0, nullptr, false, ""},
    {"INNODB", 0, nullptr, false, ""},
    {"INOUT", 0, nullptr, false, ""},
    {"INSENSITIVE", 0, nullptr, false, ""},
    {"INSERT", 0, nullptr, false, ""},
    {"INSERT_METHOD", 0, nullptr, false, ""},
    {"INT", 0, nullptr, false, ""},
    {"INT1", 0, nullptr, false, ""},
    {"INT2", 0, nullptr, false, ""},
    {"INT3", 0, nullptr, false, ""},
    {"INT4", 0, nullptr, false, ""},
    {"INT8", 0, nullptr, false, ""},
    {"INTEGER", 0, nullptr, false, ""},
    {"INTERVAL", 0, nullptr, false, ""},
    {"INTO", 0, nullptr, false, ""},
    {"IO_THREAD", 0, nullptr, false, ""},
    {"IS", 0, nullptr, false, ""},
    {"ISOLATION", 0, nullptr, false, ""},
    {"ISSUER", 0, nullptr, false, ""},
    {"ITERATE", 0, nullptr, false, ""},
    {"INVOKER", 0, nullptr, false, ""},
    {"JOIN", 0, nullptr, false, ""},
    {"KEY", 0, nullptr, false, ""},
    {"KEYS", 0, nullptr, false, ""},
    {"KILL", 0, nullptr, false, ""},
    {"LANGUAGE", 0, nullptr, false, ""},
    {"LAST", 0, nullptr, false, ""},
    {"LEADING", 0, nullptr, false, ""},
    {"LEAVE", 0, nullptr, false, ""},
    {"LEAVES", 0, nullptr, false, ""},
    {"LEFT", 0, nullptr, false, ""},
    {"LEVEL", 0, nullptr, false, ""},
    {"LIKE", 0, nullptr, false, ""},
    {"LIMIT", 0, nullptr, false, ""},
    {"LINES", 0, nullptr, false, ""},
    {"LINESTRING", 0, nullptr, false, ""},
    {"LOAD", 0, nullptr, false, ""},
    {"LOCAL", 0, nullptr, false, ""},
    {"LOCALTIME", 0, nullptr, false, ""},
    {"LOCALTIMESTAMP", 0, nullptr, false, ""},
    {"LOCK", 0, nullptr, false, ""},
    {"LOCKS", 0, nullptr, false, ""},
    {"LOGS", 0, nullptr, false, ""},
    {"LONG", 0, nullptr, false, ""},
    {"LONGBLOB", 0, nullptr, false, ""},
    {"LONGTEXT", 0, nullptr, false, ""},
    {"LOOP", 0, nullptr, false, ""},
    {"LOW_PRIORITY", 0, nullptr, false, ""},
    {"MASTER", 0, nullptr, false, ""},
    {"SOURCE_CONNECT_RETRY", 0, nullptr, false, ""},
    {"SOURCE_HOST", 0, nullptr, false, ""},
    {"SOURCE_LOG_FILE", 0, nullptr, false, ""},
    {"SOURCE_LOG_POS", 0, nullptr, false, ""},
    {"SOURCE_PASSWORD", 0, nullptr, false, ""},
    {"SOURCE_PORT", 0, nullptr, false, ""},
    {"SOURCE_SERVER_ID", 0, nullptr, false, ""},
    {"SOURCE_SSL", 0, nullptr, false, ""},
    {"SOURCE_SSL_CA", 0, nullptr, false, ""},
    {"SOURCE_SSL_CAPATH", 0, nullptr, false, ""},
    {"SOURCE_SSL_CERT", 0, nullptr, false, ""},
    {"SOURCE_SSL_CIPHER", 0, nullptr, false, ""},
    {"SOURCE_TLS_VERSION", 0, nullptr, false, ""},
    {"SOURCE_SSL_KEY", 0, nullptr, false, ""},
    {"SOURCE_USER", 0, nullptr, false, ""},
    {"MATCH", 0, nullptr, false, ""},
    {"MAX_CONNECTIONS_PER_HOUR", 0, nullptr, false, ""},
    {"MAX_QUERIES_PER_HOUR", 0, nullptr, false, ""},
    {"MAX_ROWS", 0, nullptr, false, ""},
    {"MAX_UPDATES_PER_HOUR", 0, nullptr, false, ""},
    {"MAX_USER_CONNECTIONS", 0, nullptr, false, ""},
    {"MEDIUM", 0, nullptr, false, ""},
    {"MEDIUMBLOB", 0, nullptr, false, ""},
    {"MEDIUMINT", 0, nullptr, false, ""},
    {"MEDIUMTEXT", 0, nullptr, false, ""},
    {"MERGE", 0, nullptr, false, ""},
    {"MICROSECOND", 0, nullptr, false, ""},
    {"MIDDLEINT", 0, nullptr, false, ""},
    {"MIGRATE", 0, nullptr, false, ""},
    {"MINUTE", 0, nullptr, false, ""},
    {"MINUTE_MICROSECOND", 0, nullptr, false, ""},
    {"MINUTE_SECOND", 0, nullptr, false, ""},
    {"MIN_ROWS", 0, nullptr, false, ""},
    {"MOD", 0, nullptr, false, ""},
    {"MODE", 0, nullptr, false, ""},
    {"MODIFIES", 0, nullptr, false, ""},
    {"MODIFY", 0, nullptr, false, ""},
    {"MONTH", 0, nullptr, false, ""},
    {"MULTILINESTRING", 0, nullptr, false, ""},
    {"MULTIPOINT", 0, nullptr, false, ""},
    {"MULTIPOLYGON", 0, nullptr, false, ""},
    {"MUTEX", 0, nullptr, false, ""},
    {"NAME", 0, nullptr, false, ""},
    {"NAMES", 0, nullptr, false, ""},
    {"NATIONAL", 0, nullptr, false, ""},
    {"NATURAL", 0, nullptr, false, ""},
    {"NDB", 0, nullptr, false, ""},
    {"NDBCLUSTER", 0, nullptr, false, ""},
    {"NCHAR", 0, nullptr, false, ""},
    {"NEW", 0, nullptr, false, ""},
    {"NEXT", 0, nullptr, false, ""},
    {"NO", 0, nullptr, false, ""},
    {"NONE", 0, nullptr, false, ""},
    {"NOT", 0, nullptr, false, ""},
    {"NO_WRITE_TO_BINLOG", 0, nullptr, false, ""},
    {"NULL", 0, nullptr, false, ""},
    {"NUMERIC", 0, nullptr, false, ""},
    {"NVARCHAR", 0, nullptr, false, ""},
    {"OFFSET", 0, nullptr, false, ""},
    {"ON", 0, nullptr, false, ""},
    {"ONE", 0, nullptr, false, ""},
    {"ONE_SHOT", 0, nullptr, false, ""},
    {"OPEN", 0, nullptr, false, ""},
    {"OPTIMIZE", 0, nullptr, false, ""},
    {"OPTION", 0, nullptr, false, ""},
    {"OPTIONALLY", 0, nullptr, false, ""},
    {"OR", 0, nullptr, false, ""},
    {"ORDER", 0, nullptr, false, ""},
    {"OUT", 0, nullptr, false, ""},
    {"OUTER", 0, nullptr, false, ""},
    {"OUTFILE", 0, nullptr, false, ""},
    {"PACK_KEYS", 0, nullptr, false, ""},
    {"PARTIAL", 0, nullptr, false, ""},
    {"PASSWORD", 0, nullptr, false, ""},
    {"PHASE", 0, nullptr, false, ""},
    {"POINT", 0, nullptr, false, ""},
    {"POLYGON", 0, nullptr, false, ""},
    {"PRECISION", 0, nullptr, false, ""},
    {"PREPARE", 0, nullptr, false, ""},
    {"PREV", 0, nullptr, false, ""},
    {"PRIMARY", 0, nullptr, false, ""},
    {"PRIVILEGES", 0, nullptr, false, ""},
    {"PROCEDURE", 0, nullptr, false, ""},
    {"PROCESS", 0, nullptr, false, ""},
    {"PROCESSLIST", 0, nullptr, false, ""},
    {"PURGE", 0, nullptr, false, ""},
    {"QUALIFY", 0, nullptr, false, ""},
    {"QUARTER", 0, nullptr, false, ""},
    {"QUERY", 0, nullptr, false, ""},
    {"QUICK", 0, nullptr, false, ""},
    {"READ", 0, nullptr, false, ""},
    {"READS", 0, nullptr, false, ""},
    {"REAL", 0, nullptr, false, ""},
    {"RECOVER", 0, nullptr, false, ""},
    {"REDUNDANT", 0, nullptr, false, ""},
    {"REFERENCES", 0, nullptr, false, ""},
    {"REGEXP", 0, nullptr, false, ""},
    {"RELAY_LOG_FILE", 0, nullptr, false, ""},
    {"RELAY_LOG_POS", 0, nullptr, false, ""},
    {"RELAY_THREAD", 0, nullptr, false, ""},
    {"RELEASE", 0, nullptr, false, ""},
    {"RELOAD", 0, nullptr, false, ""},
    {"RENAME", 0, nullptr, false, ""},
    {"REPAIR", 0, nullptr, false, ""},
    {"REPEATABLE", 0, nullptr, false, ""},
    {"REPLACE", 0, nullptr, false, ""},
    {"REPLICATION", 0, nullptr, false, ""},
    {"REPEAT", 0, nullptr, false, ""},
    {"REQUIRE", 0, nullptr, false, ""},
    {"RESET", 0, nullptr, false, ""},
    {"RESTORE", 0, nullptr, false, ""},
    {"RESTRICT", 0, nullptr, false, ""},
    {"RESUME", 0, nullptr, false, ""},
    {"RETURN", 0, nullptr, false, ""},
    {"RETURNS", 0, nullptr, false, ""},
    {"REVOKE", 0, nullptr, false, ""},
    {"RIGHT", 0, nullptr, false, ""},
    {"RLIKE", 0, nullptr, false, ""},
    {"ROLLBACK", 0, nullptr, false, ""},
    {"ROLLUP", 0, nullptr, false, ""},
    {"ROUTINE", 0, nullptr, false, ""},
    {"ROW", 0, nullptr, false, ""},
    {"ROWS", 0, nullptr, false, ""},
    {"ROW_FORMAT", 0, nullptr, false, ""},
    {"RTREE", 0, nullptr, false, ""},
    {"SAVEPOINT", 0, nullptr, false, ""},
    {"SCHEMA", 0, nullptr, false, ""},
    {"SCHEMAS", 0, nullptr, false, ""},
    {"SECOND", 0, nullptr, false, ""},
    {"SECOND_MICROSECOND", 0, nullptr, false, ""},
    {"SECURITY", 0, nullptr, false, ""},
    {"SELECT", 0, nullptr, false, ""},
    {"SENSITIVE", 0, nullptr, false, ""},
    {"SEPARATOR", 0, nullptr, false, ""},
    {"SERIAL", 0, nullptr, false, ""},
    {"SERIALIZABLE", 0, nullptr, false, ""},
    {"SESSION", 0, nullptr, false, ""},
    {"SET", 0, nullptr, false, ""},
    {"SHARE", 0, nullptr, false, ""},
    {"SHOW", 0, nullptr, false, ""},
    {"SHUTDOWN", 0, nullptr, false, ""},
    {"SIGNED", 0, nullptr, false, ""},
    {"SIMPLE", 0, nullptr, false, ""},
    {"SLAVE", 0, nullptr, false, ""},
    {"SNAPSHOT", 0, nullptr, false, ""},
    {"SMALLINT", 0, nullptr, false, ""},
    {"SOME", 0, nullptr, false, ""},
    {"SONAME", 0, nullptr, false, ""},
    {"SOUNDS", 0, nullptr, false, ""},
    {"SPATIAL", 0, nullptr, false, ""},
    {"SPECIFIC", 0, nullptr, false, ""},
    {"SQL", 0, nullptr, false, ""},
    {"SQLEXCEPTION", 0, nullptr, false, ""},
    {"SQLSTATE", 0, nullptr, false, ""},
    {"SQLWARNING", 0, nullptr, false, ""},
    {"SQL_BIG_RESULT", 0, nullptr, false, ""},
    {"SQL_BUFFER_RESULT", 0, nullptr, false, ""},
    {"SQL_CALC_FOUND_ROWS", 0, nullptr, false, ""},
    {"SQL_NO_CACHE", 0, nullptr, false, ""},
    {"SQL_SMALL_RESULT", 0, nullptr, false, ""},
    {"SQL_THREAD", 0, nullptr, false, ""},
    {"SQL_TSI_SECOND", 0, nullptr, false, ""},
    {"SQL_TSI_MINUTE", 0, nullptr, false, ""},
    {"SQL_TSI_HOUR", 0, nullptr, false, ""},
    {"SQL_TSI_DAY", 0, nullptr, false, ""},
    {"SQL_TSI_WEEK", 0, nullptr, false, ""},
    {"SQL_TSI_MONTH", 0, nullptr, false, ""},
    {"SQL_TSI_QUARTER", 0, nullptr, false, ""},
    {"SQL_TSI_YEAR", 0, nullptr, false, ""},
    {"SSL", 0, nullptr, false, ""},
    {"START", 0, nullptr, false, ""},
    {"STARTING", 0, nullptr, false, ""},
    {"STATUS", 0, nullptr, false, ""},
    {"STOP", 0, nullptr, false, ""},
    {"STORAGE", 0, nullptr, false, ""},
    {"STRAIGHT_JOIN", 0, nullptr, false, ""},
    {"STRING", 0, nullptr, false, ""},
    {"STRIPED", 0, nullptr, false, ""},
    {"SUBJECT", 0, nullptr, false, ""},
    {"SUPER", 0, nullptr, false, ""},
    {"SUSPEND", 0, nullptr, false, ""},
    {"TABLE", 0, nullptr, false, ""},
    {"TABLES", 0, nullptr, false, ""},
    {"TABLESPACE", 0, nullptr, false, ""},
    {"TEMPORARY", 0, nullptr, false, ""},
    {"TEMPTABLE", 0, nullptr, false, ""},
    {"TERMINATED", 0, nullptr, false, ""},
    {"TEXT", 0, nullptr, false, ""},
    {"THEN", 0, nullptr, false, ""},
    {"TIME", 0, nullptr, false, ""},
    {"TIMESTAMP", 0, nullptr, false, ""},
    {"TIMESTAMPADD", 0, nullptr, false, ""},
    {"TIMESTAMPDIFF", 0, nullptr, false, ""},
    {"TINYBLOB", 0, nullptr, false, ""},
    {"TINYINT", 0, nullptr, false, ""},
    {"TINYTEXT", 0, nullptr, false, ""},
    {"TO", 0, nullptr, false, ""},
    {"TRAILING", 0, nullptr, false, ""},
    {"TRANSACTION", 0, nullptr, false, ""},
    {"TRIGGER", 0, nullptr, false, ""},
    {"TRIGGERS", 0, nullptr, false, ""},
    {"TRUE", 0, nullptr, false, ""},
    {"TRUNCATE", 0, nullptr, false, ""},
    {"TYPE", 0, nullptr, false, ""},
    {"TYPES", 0, nullptr, false, ""},
    {"UNCOMMITTED", 0, nullptr, false, ""},
    {"UNDEFINED", 0, nullptr, false, ""},
    {"UNDO", 0, nullptr, false, ""},
    {"UNICODE", 0, nullptr, false, ""},
    {"UNION", 0, nullptr, false, ""},
    {"UNIQUE", 0, nullptr, false, ""},
    {"UNKNOWN", 0, nullptr, false, ""},
    {"UNLOCK", 0, nullptr, false, ""},
    {"UNSIGNED", 0, nullptr, false, ""},
    {"UNTIL", 0, nullptr, false, ""},
    {"UPDATE", 0, nullptr, false, ""},
    {"UPGRADE", 0, nullptr, false, ""},
    {"USAGE", 0, nullptr, false, ""},
    {"USE", 0, nullptr, false, ""},
    {"USER", 0, nullptr, false, ""},
    {"USER_RESOURCES", 0, nullptr, false, ""},
    {"USE_FRM", 0, nullptr, false, ""},
    {"USING", 0, nullptr, false, ""},
    {"UTC_DATE", 0, nullptr, false, ""},
    {"UTC_TIME", 0, nullptr, false, ""},
    {"UTC_TIMESTAMP", 0, nullptr, false, ""},
    {"VALUE", 0, nullptr, false, ""},
    {"VALUES", 0, nullptr, false, ""},
    {"VARBINARY", 0, nullptr, false, ""},
    {"VARCHAR", 0, nullptr, false, ""},
    {"VARCHARACTER", 0, nullptr, false, ""},
    {"VARIABLES", 0, nullptr, false, ""},
    {"VARYING", 0, nullptr, false, ""},
    {"WARNINGS", 0, nullptr, false, ""},
    {"WEEK", 0, nullptr, false, ""},
    {"WHEN", 0, nullptr, false, ""},
    {"WHERE", 0, nullptr, false, ""},
    {"WHILE", 0, nullptr, false, ""},
    {"VIEW", 0, nullptr, false, ""},
    {"WITH", 0, nullptr, false, ""},
    {"WORK", 0, nullptr, false, ""},
    {"WRITE", 0, nullptr, false, ""},
    {"X509", 0, nullptr, false, ""},
    {"XOR", 0, nullptr, false, ""},
    {"XA", 0, nullptr, false, ""},
    {"YEAR", 0, nullptr, false, ""},
    {"YEAR_MONTH", 0, nullptr, false, ""},
    {"ZEROFILL", 0, nullptr, false, ""},
    {"ABS", 0, nullptr, false, ""},
    {"ACOS", 0, nullptr, false, ""},
    {"ADDDATE", 0, nullptr, false, ""},
    {"ADDTIME", 0, nullptr, false, ""},
    {"AES_ENCRYPT", 0, nullptr, false, ""},
    {"AES_DECRYPT", 0, nullptr, false, ""},
    {"AREA", 0, nullptr, false, ""},
    {"ASIN", 0, nullptr, false, ""},
    {"ASBINARY", 0, nullptr, false, ""},
    {"ASTEXT", 0, nullptr, false, ""},
    {"ASWKB", 0, nullptr, false, ""},
    {"ASWKT", 0, nullptr, false, ""},
    {"ATAN", 0, nullptr, false, ""},
    {"ATAN2", 0, nullptr, false, ""},
    {"BENCHMARK", 0, nullptr, false, ""},
    {"BIN", 0, nullptr, false, ""},
    {"BIT_COUNT", 0, nullptr, false, ""},
    {"BIT_OR", 0, nullptr, false, ""},
    {"BIT_AND", 0, nullptr, false, ""},
    {"BIT_XOR", 0, nullptr, false, ""},
    {"CAST", 0, nullptr, false, ""},
    {"CEIL", 0, nullptr, false, ""},
    {"CEILING", 0, nullptr, false, ""},
    {"BIT_LENGTH", 0, nullptr, false, ""},
    {"CENTROID", 0, nullptr, false, ""},
    {"CHAR_LENGTH", 0, nullptr, false, ""},
    {"CHARACTER_LENGTH", 0, nullptr, false, ""},
    {"COALESCE", 0, nullptr, false, ""},
    {"COERCIBILITY", 0, nullptr, false, ""},
    {"COMPRESS", 0, nullptr, false, ""},
    {"CONCAT", 0, nullptr, false, ""},
    {"CONCAT_WS", 0, nullptr, false, ""},
    {"CONNECTION_ID", 0, nullptr, false, ""},
    {"CONV", 0, nullptr, false, ""},
    {"CONVERT_TZ", 0, nullptr, false, ""},
    {"COUNT", 0, nullptr, false, ""},
    {"COS", 0, nullptr, false, ""},
    {"COT", 0, nullptr, false, ""},
    {"CRC32", 0, nullptr, false, ""},
    {"CROSSES", 0, nullptr, false, ""},
    {"CURDATE", 0, nullptr, false, ""},
    {"CURTIME", 0, nullptr, false, ""},
    {"DATE_ADD", 0, nullptr, false, ""},
    {"DATEDIFF", 0, nullptr, false, ""},
    {"DATE_FORMAT", 0, nullptr, false, ""},
    {"DATE_SUB", 0, nullptr, false, ""},
    {"DAYNAME", 0, nullptr, false, ""},
    {"DAYOFMONTH", 0, nullptr, false, ""},
    {"DAYOFWEEK", 0, nullptr, false, ""},
    {"DAYOFYEAR", 0, nullptr, false, ""},
    {"DEGREES", 0, nullptr, false, ""},
    {"DIMENSION", 0, nullptr, false, ""},
    {"DISJOINT", 0, nullptr, false, ""},
    {"ELT", 0, nullptr, false, ""},
    {"ENDPOINT", 0, nullptr, false, ""},
    {"ENVELOPE", 0, nullptr, false, ""},
    {"EQUALS", 0, nullptr, false, ""},
    {"EXTERIORRING", 0, nullptr, false, ""},
    {"EXTRACT", 0, nullptr, false, ""},
    {"EXP", 0, nullptr, false, ""},
    {"EXPORT_SET", 0, nullptr, false, ""},
    {"FIELD", 0, nullptr, false, ""},
    {"FIND_IN_SET", 0, nullptr, false, ""},
    {"FLOOR", 0, nullptr, false, ""},
    {"FORMAT", 0, nullptr, false, ""},
    {"FOUND_ROWS", 0, nullptr, false, ""},
    {"FROM_DAYS", 0, nullptr, false, ""},
    {"FROM_UNIXTIME", 0, nullptr, false, ""},
    {"GET_LOCK", 0, nullptr, false, ""},
    {"GEOMETRYN", 0, nullptr, false, ""},
    {"GEOMETRYTYPE", 0, nullptr, false, ""},
    {"GEOMCOLLFROMTEXT", 0, nullptr, false, ""},
    {"GEOMCOLLFROMWKB", 0, nullptr, false, ""},
    {"GEOMETRYCOLLECTIONFROMTEXT", 0, nullptr, false, ""},
    {"GEOMETRYCOLLECTIONFROMWKB", 0, nullptr, false, ""},
    {"GEOMETRYFROMTEXT", 0, nullptr, false, ""},
    {"GEOMETRYFROMWKB", 0, nullptr, false, ""},
    {"GEOMFROMTEXT", 0, nullptr, false, ""},
    {"GEOMFROMWKB", 0, nullptr, false, ""},
    {"GLENGTH", 0, nullptr, false, ""},
    {"GREATEST", 0, nullptr, false, ""},
    {"GROUP_CONCAT", 0, nullptr, false, ""},
    {"GROUP_UNIQUE_USERS", 0, nullptr, false, ""},
    {"HEX", 0, nullptr, false, ""},
    {"IFNULL", 0, nullptr, false, ""},
    {"INET_ATON", 0, nullptr, false, ""},
    {"INET_NTOA", 0, nullptr, false, ""},
    {"INSTR", 0, nullptr, false, ""},
    {"INTERIORRINGN", 0, nullptr, false, ""},
    {"INTERSECTS", 0, nullptr, false, ""},
    {"ISCLOSED", 0, nullptr, false, ""},
    {"ISEMPTY", 0, nullptr, false, ""},
    {"ISNULL", 0, nullptr, false, ""},
    {"IS_FREE_LOCK", 0, nullptr, false, ""},
    {"IS_USED_LOCK", 0, nullptr, false, ""},
    {"JSON_ARRAY_APPEND", 0, nullptr, false, ""},
    {"JSON_ARRAY", 0, nullptr, false, ""},
    {"JSON_CONTAINS", 0, nullptr, false, ""},
    {"JSON_DEPTH", 0, nullptr, false, ""},
    {"JSON_EXTRACT", 0, nullptr, false, ""},
    {"JSON_INSERT", 0, nullptr, false, ""},
    {"JSON_KEYS", 0, nullptr, false, ""},
    {"JSON_LENGTH", 0, nullptr, false, ""},
    {"JSON_MERGE", 0, nullptr, false, ""},
    {"JSON_QUOTE", 0, nullptr, false, ""},
    {"JSON_REPLACE", 0, nullptr, false, ""},
    {"JSON_ROWOBJECT", 0, nullptr, false, ""},
    {"JSON_SEARCH", 0, nullptr, false, ""},
    {"JSON_SET", 0, nullptr, false, ""},
    {"JSON_TYPE", 0, nullptr, false, ""},
    {"JSON_UNQUOTE", 0, nullptr, false, ""},
    {"JSON_VALID", 0, nullptr, false, ""},
    {"JSON_CONTAINS_PATH", 0, nullptr, false, ""},
    {"LAST_INSERT_ID", 0, nullptr, false, ""},
    {"ISSIMPLE", 0, nullptr, false, ""},
    {"LAST_DAY", 0, nullptr, false, ""},
    {"LCASE", 0, nullptr, false, ""},
    {"LEAST", 0, nullptr, false, ""},
    {"LENGTH", 0, nullptr, false, ""},
    {"LN", 0, nullptr, false, ""},
    {"LINEFROMTEXT", 0, nullptr, false, ""},
    {"LINEFROMWKB", 0, nullptr, false, ""},
    {"LINESTRINGFROMTEXT", 0, nullptr, false, ""},
    {"LINESTRINGFROMWKB", 0, nullptr, false, ""},
    {"LOAD_FILE", 0, nullptr, false, ""},
    {"LOCATE", 0, nullptr, false, ""},
    {"LOG", 0, nullptr, false, ""},
    {"LOG2", 0, nullptr, false, ""},
    {"LOG10", 0, nullptr, false, ""},
    {"LOWER", 0, nullptr, false, ""},
    {"LPAD", 0, nullptr, false, ""},
    {"LTRIM", 0, nullptr, false, ""},
    {"MAKE_SET", 0, nullptr, false, ""},
    {"MAKEDATE", 0, nullptr, false, ""},
    {"MAKETIME", 0, nullptr, false, ""},
    {"SOURCE_POS_WAIT", 0, nullptr, false, ""},
    {"MAX", 0, nullptr, false, ""},
    {"MBRCONTAINS", 0, nullptr, false, ""},
    {"MBRDISJOINT", 0, nullptr, false, ""},
    {"MBREQUAL", 0, nullptr, false, ""},
    {"MBRINTERSECTS", 0, nullptr, false, ""},
    {"MBROVERLAPS", 0, nullptr, false, ""},
    {"MBRTOUCHES", 0, nullptr, false, ""},
    {"MBRWITHIN", 0, nullptr, false, ""},
    {"MD5", 0, nullptr, false, ""},
    {"MID", 0, nullptr, false, ""},
    {"MIN", 0, nullptr, false, ""},
    {"MLINEFROMTEXT", 0, nullptr, false, ""},
    {"MLINEFROMWKB", 0, nullptr, false, ""},
    {"MPOINTFROMTEXT", 0, nullptr, false, ""},
    {"MPOINTFROMWKB", 0, nullptr, false, ""},
    {"MPOLYFROMTEXT", 0, nullptr, false, ""},
    {"MPOLYFROMWKB", 0, nullptr, false, ""},
    {"MONTHNAME", 0, nullptr, false, ""},
    {"MULTILINESTRINGFROMTEXT", 0, nullptr, false, ""},
    {"MULTILINESTRINGFROMWKB", 0, nullptr, false, ""},
    {"MULTIPOINTFROMTEXT", 0, nullptr, false, ""},
    {"MULTIPOINTFROMWKB", 0, nullptr, false, ""},
    {"MULTIPOLYGONFROMTEXT", 0, nullptr, false, ""},
    {"MULTIPOLYGONFROMWKB", 0, nullptr, false, ""},
    {"NAME_CONST", 0, nullptr, false, ""},
    {"NOW", 0, nullptr, false, ""},
    {"NULLIF", 0, nullptr, false, ""},
    {"NUMGEOMETRIES", 0, nullptr, false, ""},
    {"NUMINTERIORRINGS", 0, nullptr, false, ""},
    {"NUMPOINTS", 0, nullptr, false, ""},
    {"OCTET_LENGTH", 0, nullptr, false, ""},
    {"OCT", 0, nullptr, false, ""},
    {"ORD", 0, nullptr, false, ""},
    {"OVERLAPS", 0, nullptr, false, ""},
    {"PERIOD_ADD", 0, nullptr, false, ""},
    {"PERIOD_DIFF", 0, nullptr, false, ""},
    {"PI", 0, nullptr, false, ""},
    {"POINTFROMTEXT", 0, nullptr, false, ""},
    {"POINTFROMWKB", 0, nullptr, false, ""},
    {"POINTN", 0, nullptr, false, ""},
    {"POLYFROMTEXT", 0, nullptr, false, ""},
    {"POLYFROMWKB", 0, nullptr, false, ""},
    {"POLYGONFROMTEXT", 0, nullptr, false, ""},
    {"POLYGONFROMWKB", 0, nullptr, false, ""},
    {"POSITION", 0, nullptr, false, ""},
    {"POW", 0, nullptr, false, ""},
    {"POWER", 0, nullptr, false, ""},
    {"QUOTE", 0, nullptr, false, ""},
    {"RADIANS", 0, nullptr, false, ""},
    {"RAND", 0, nullptr, false, ""},
    {"RELEASE_LOCK", 0, nullptr, false, ""},
    {"REVERSE", 0, nullptr, false, ""},
    {"ROUND", 0, nullptr, false, ""},
    {"ROW_COUNT", 0, nullptr, false, ""},
    {"RPAD", 0, nullptr, false, ""},
    {"RTRIM", 0, nullptr, false, ""},
    {"SEC_TO_TIME", 0, nullptr, false, ""},
    {"SESSION_USER", 0, nullptr, false, ""},
    {"SUBDATE", 0, nullptr, false, ""},
    {"SIGN", 0, nullptr, false, ""},
    {"SIN", 0, nullptr, false, ""},
    {"SHA", 0, nullptr, false, ""},
    {"SHA1", 0, nullptr, false, ""},
    {"SLEEP", 0, nullptr, false, ""},
    {"SOUNDEX", 0, nullptr, false, ""},
    {"SPACE", 0, nullptr, false, ""},
    {"SQRT", 0, nullptr, false, ""},
    {"SRID", 0, nullptr, false, ""},
    {"STARTPOINT", 0, nullptr, false, ""},
    {"STD", 0, nullptr, false, ""},
    {"STDDEV", 0, nullptr, false, ""},
    {"STDDEV_POP", 0, nullptr, false, ""},
    {"STDDEV_SAMP", 0, nullptr, false, ""},
    {"STR_TO_DATE", 0, nullptr, false, ""},
    {"STRCMP", 0, nullptr, false, ""},
    {"SUBSTR", 0, nullptr, false, ""},
    {"SUBSTRING", 0, nullptr, false, ""},
    {"SUBSTRING_INDEX", 0, nullptr, false, ""},
    {"SUBTIME", 0, nullptr, false, ""},
    {"SUM", 0, nullptr, false, ""},
    {"SYSDATE", 0, nullptr, false, ""},
    {"SYSTEM_USER", 0, nullptr, false, ""},
    {"TAN", 0, nullptr, false, ""},
    {"TIME_FORMAT", 0, nullptr, false, ""},
    {"TIME_TO_SEC", 0, nullptr, false, ""},
    {"TIMEDIFF", 0, nullptr, false, ""},
    {"TO_DAYS", 0, nullptr, false, ""},
    {"TOUCHES", 0, nullptr, false, ""},
    {"TRIM", 0, nullptr, false, ""},
    {"TO_VECTOR", 0, nullptr, false, ""},
    {"STRING_TO_VECTOR", 0, nullptr, false, ""},
    {"FROM_VECTOR", 0, nullptr, false, ""},
    {"VECTOR_TO_STRING", 0, nullptr, false, ""},
    {"VECTOR_DIM", 0, nullptr, false, ""},
    {"UCASE", 0, nullptr, false, ""},
    {"UNCOMPRESS", 0, nullptr, false, ""},
    {"UNCOMPRESSED_LENGTH", 0, nullptr, false, ""},
    {"UNHEX", 0, nullptr, false, ""},
    {"UNIQUE_USERS", 0, nullptr, false, ""},
    {"UNIX_TIMESTAMP", 0, nullptr, false, ""},
    {"UPPER", 0, nullptr, false, ""},
    {"UUID", 0, nullptr, false, ""},
    {"VARIANCE", 0, nullptr, false, ""},
    {"VAR_POP", 0, nullptr, false, ""},
    {"VAR_SAMP", 0, nullptr, false, ""},
    {"VERSION", 0, nullptr, false, ""},
    {"WEEKDAY", 0, nullptr, false, ""},
    {"WEEKOFYEAR", 0, nullptr, false, ""},
    {"WITHIN", 0, nullptr, false, ""},
    {"X", 0, nullptr, false, ""},
    {"Y", 0, nullptr, false, ""},
    {"YEARWEEK", 0, nullptr, false, ""},
    /* end sentinel */
    {(char *)nullptr, 0, nullptr, false, ""}};

static const char *load_default_groups[] = {"mysql", "client", nullptr};

#ifdef HAVE_READLINE
/*
 HIST_ENTRY is defined for libedit, but not for the real readline
 Need to redefine it for real readline to find it
*/
#if !defined(HAVE_HIST_ENTRY)
typedef struct _hist_entry {
  const char *line;
  const char *data;
} HIST_ENTRY;
#endif

static int not_in_history(const char *line);
static void initialize_readline(char *name);
#endif /* HAVE_READLINE */

static COMMANDS *find_command(char *name);
static COMMANDS *find_command(char cmd_char);
static bool add_line(String &buffer, char *line, size_t line_length,
                     char *in_string, bool *ml_comment, bool truncated);
static void remove_cntrl(String *buffer);
static void print_table_data(MYSQL_RES *result);
static void print_table_data_html(MYSQL_RES *result);
static void print_table_data_xml(MYSQL_RES *result);
static void print_tab_data(MYSQL_RES *result);
static void print_table_data_vertically(MYSQL_RES *result);
static void print_warnings();
static void end_timer(ulonglong start_time, char *buff);
static void mysql_end_timer(ulonglong start_time, char *buff);
static void nice_time(double sec, char *buff, bool part_second);
static void kill_query(const char *reason);
extern "C" void mysql_end(int sig);
extern "C" void handle_ctrlc_signal(int);
extern "C" void handle_quit_signal(int sig);
#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL)
static void window_resize(int);
#endif

const char DELIMITER_NAME[] = "delimiter";
const uint DELIMITER_NAME_LEN = sizeof(DELIMITER_NAME) - 1;
inline bool is_delimiter_command(const char *name, ulong len) {
  /*
    Delimiter command has a parameter, so the length of the whole command
    is larger than DELIMITER_NAME_LEN.  We don't care the parameter, so
    only name(first DELIMITER_NAME_LEN bytes) is checked.
  */
  return (len >= DELIMITER_NAME_LEN &&
          !my_strnncoll(charset_info, pointer_cast<const uchar *>(name),
                        DELIMITER_NAME_LEN,
                        pointer_cast<const uchar *>(DELIMITER_NAME),
                        DELIMITER_NAME_LEN));
}

/**
   Get the index of a command in the commands array.

   @param cmd_char    Short form command.

   @return int
     The index of the command is returned if it is found, else -1 is returned.
*/
inline int get_command_index(char cmd_char) {
  /*
    All client-specific commands are in the first part of commands array
    and have a function to implement it.
  */
  for (uint i = 0; commands[i].func != nullptr; i++)
    if (commands[i].cmd_char == cmd_char) return i;
  return -1;
}

static int delimiter_index = -1;
static int charset_index = -1;
static bool disable_commands = false;

#ifdef _WIN32
BOOL windows_ctrl_handler(DWORD fdwCtrlType) {
  switch (fdwCtrlType) {
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:
      handle_ctrlc_signal(SIGINT);
      /* Indicate that signal has beed handled. */
      return true;
    case CTRL_CLOSE_EVENT:
    case CTRL_LOGOFF_EVENT:
    case CTRL_SHUTDOWN_EVENT:
      handle_quit_signal(SIGINT + 1);
  }
  /* Pass signal to the next control handler function. */
  return false;
}
#endif

static bool server_supports_dollar_quote(MYSQL *con) {
  // This query will fail with parse error if dollar quotes are supported
  return mysql_real_query(con, "select $$", 9) &&
         mysql_errno(&mysql_handle) == ER_PARSE_ERROR;
}

int main(int argc, char *argv[]) {
  char buff[80];

  MY_INIT(argv[0]);
  DBUG_TRACE;
  DBUG_PROCESS(argv[0]);

  charset_index = get_command_index('C');
  delimiter_index = get_command_index('d');
  delimiter_str = delimiter;
  default_prompt = my_strdup(
      PSI_NOT_INSTRUMENTED,
      getenv("MYSQL_PS1") ? getenv("MYSQL_PS1") : "mysql> ", MYF(MY_WME));
  current_prompt = my_strdup(PSI_NOT_INSTRUMENTED, default_prompt, MYF(MY_WME));
  prompt_counter = 0;

  outfile[0] = 0;              // no (default) outfile
  my_stpcpy(pager, "stdout");  // the default, if --pager wasn't given
  {
    char *tmp = getenv("PAGER");
    if (tmp && strlen(tmp)) {
      default_pager_set = true;
      my_stpcpy(default_pager, tmp);
    }
  }
  if (!isatty(0) || !isatty(1)) {
    status.batch = true;
    opt_silent = 1;
    ignore_errors = false;
  } else
    status.add_to_history = true;
  status.exit_status = 1;

  {
    /*
     The file descriptor-layer may be out-of-sync with the file-number layer,
     so we make sure that "stdout" is really open.  If its file is closed then
     explicitly close the FD layer.
    */
    int stdout_fileno_copy;
    stdout_fileno_copy = dup(fileno(stdout)); /* Okay if fileno fails. */
    if (stdout_fileno_copy == -1) {
      fclose(stdout);
#ifdef LINUX_ALPINE
      // On Alpine linux we need to open a dummy file, so that the first
      // call to socket() does not get file number 1
      // If socket gets file number 1, then everything printed to stdout
      // will be sent back to the server over the socket connection.
      fopen("/dev/null", "r");
#endif
    } else
      close(stdout_fileno_copy); /* Clean up dup(). */
  }

#ifdef _WIN32
  /* Convert command line parameters from UTF16LE to UTF8MB4. */
  my_win_translate_command_line_args(&my_charset_utf8mb4_bin, &argc, &argv);
#endif

  my_getopt_use_args_separator = true;
  if (load_defaults("my", load_default_groups, &argc, &argv, &argv_alloc)) {
    my_end(0);
    return EXIT_FAILURE;
  }
  my_getopt_use_args_separator = false;

  get_current_os_user();
  get_current_os_sudouser();

  if (get_options(&argc, &argv)) {
    my_end(0);
    return EXIT_FAILURE;
  }

  if (status.batch && !status.line_buff &&
      !(status.line_buff = batch_readline_init(MAX_BATCH_BUFFER_SIZE, stdin))) {
    put_info(
        "Can't initialize batch_readline - may be the input source is "
        "a directory or a block device.",
        INFO_ERROR, 0);
    my_end(0);
    return EXIT_FAILURE;
  }
  if (!opt_binary_as_hex_set_explicitly && isatty(0) && isatty(1))
    opt_binhex = true;
  if (mysql_server_init(0, nullptr, nullptr)) {
    put_error(nullptr);
    my_end(0);
    return EXIT_FAILURE;
  }
  glob_buffer.mem_realloc((status.batch) ? batch_io_size : 512);
  completion_hash_init(&ht, 128);
  memset(&mysql_handle, 0, sizeof(mysql_handle));
  global_attrs = new client_query_attributes();

  if (opt_tel_plugin) {
    /*
      mysql_load_plugin() does not actually need a connection,
      it only needs the plugin_directory to find the plugin.
    */
    MYSQL not_connected;
    memset(&not_connected, 0, sizeof(not_connected));
    mysql_options(&not_connected, MYSQL_PLUGIN_DIR, opt_plugin_dir);
    /*
      Load a telemetry plugin if required.
      The plugin may consume command lines arguments in argc, argv.
    */
    struct st_mysql_client_plugin *tel_plugin = mysql_load_plugin(
        &not_connected, opt_tel_plugin_name, MYSQL_CLIENT_TELEMETRY_PLUGIN, 5,
        my_defaults_file, my_defaults_group_suffix, my_defaults_extra_file,
        &argc, argv);
    if (!tel_plugin) {
      /*
        Print the actual root cause,
        it contains details about why dlopen() failed.
      */
      put_info(mysql_error(&not_connected), INFO_ERROR);
      return EXIT_FAILURE;
    }

    telemetry_client_attrs = new client_query_attributes();
    snprintf(glob_buffer.ptr(), glob_buffer.alloced_length(),
             "Telemetry plugin <%s> is loaded.\n", opt_tel_plugin_name);
    put_info(glob_buffer.ptr(), INFO_INFO);
  }

  /*
    Now that every plugin (i.e., client_telemetry) consumed
    plugin specific options,
    make sure there are no unknown options remaining.
  */
  my_getopt_skip_unknown = false;
  const int ho_error =
      handle_options(&argc, &argv, my_empty_options, get_one_option);
  if (ho_error) {
    exit(ho_error);
  }

  if (argc > 1) {
    usage(0);
    exit(1);
  }

  if (argc == 1) {
    skip_updates = false;
    my_free(current_db);
    current_db = my_strdup(PSI_NOT_INSTRUMENTED, *argv, MYF(MY_WME));
  }

  if (sql_connect(current_host, current_db, current_user, opt_silent)) {
    quick = true;  // Avoid history
    status.exit_status = 1;
    mysql_end(-1);
  }
  if (!status.batch) ignore_errors = true;  // Don't abort monitor

#ifndef _WIN32
  signal(SIGINT, handle_ctrlc_signal);  // Catch SIGINT to clean up
  signal(SIGQUIT, mysql_end);           // Catch SIGQUIT to clean up
  signal(SIGHUP, handle_quit_signal);   // Catch SIGHUP to clean up
#else
  SetConsoleCtrlHandler((PHANDLER_ROUTINE)windows_ctrl_handler, true);
#endif

#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL)
  /* Readline will call this if it installs a handler */
  signal(SIGWINCH, window_resize);
  /* call the SIGWINCH handler to get the default term width */
  window_resize(0);
#endif

  put_info("Welcome to the MySQL monitor.  Commands end with ; or \\g.",
           INFO_INFO);
  snprintf(glob_buffer.ptr(), glob_buffer.alloced_length(),
           "Your MySQL connection id is %lu\nServer version: %s\n",
           mysql_thread_id(&mysql_handle),
           server_version_string(&mysql_handle));
  put_info(glob_buffer.ptr(), INFO_INFO);

  put_info(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"), INFO_INFO);

  if (!status.batch) {
    // history ignore patterns are initialized to default values
    ignore_matcher.add_patterns(HI_DEFAULTS);

    /*
      Additional patterns may be supplied using either --histignore option or
      MYSQL_HISTIGNORE environment variable. If supplied, they'll get appended
      to the default patterns. In case both are specified, pattern(s) supplied
      using --histignore option will be used.
    */
    if (opt_histignore)
      ignore_matcher.add_patterns(opt_histignore);
    else if (getenv("MYSQL_HISTIGNORE"))
      ignore_matcher.add_patterns(getenv("MYSQL_HISTIGNORE"));

#ifdef HAVE_READLINE
    if (!quick) {
      initialize_readline(const_cast<char *>(my_progname));

      /* read-history from file, default ~/.mysql_history*/
      if (getenv("MYSQL_HISTFILE"))
        histfile = my_strdup(PSI_NOT_INSTRUMENTED, getenv("MYSQL_HISTFILE"),
                             MYF(MY_WME));
      else if (getenv("HOME")) {
        histfile = (char *)my_malloc(
            PSI_NOT_INSTRUMENTED,
            (uint)strlen(getenv("HOME")) + (uint)strlen("/.mysql_history") + 2,
            MYF(MY_WME));
        if (histfile) sprintf(histfile, "%s/.mysql_history", getenv("HOME"));
        char link_name[FN_REFLEN];
        if (my_readlink(link_name, histfile, 0) == 0 &&
            strncmp(link_name, "/dev/null", 9) == 0) {
          /* The .mysql_history file is a symlink to /dev/null, don't use it */
          my_free(histfile);
          histfile = nullptr;
        }
      }

      /* We used to suggest setting MYSQL_HISTFILE=/dev/null. */
      if (histfile && strncmp(histfile, "/dev/null", 9) == 0)
        histfile = nullptr;

      if (histfile && histfile[0]) {
        if (verbose) tee_fprintf(stdout, "Reading history-file %s\n", histfile);
        read_history(histfile);
        histfile_tmp = (char *)my_malloc(
            PSI_NOT_INSTRUMENTED, (uint)strlen(histfile) + 5, MYF(MY_WME));
        if (!histfile_tmp) {
          fprintf(stderr, "Couldn't allocate memory for temp histfile!\n");
          return EXIT_FAILURE;
        }
        sprintf(histfile_tmp, "%s.TMP", histfile);
      }
    }
#endif
  }

  sprintf(
      buff, "%s",
      "Type 'help;' or '\\h' for help. Type '\\c' to clear the current input "
      "statement.\n");
  put_info(buff, INFO_INFO);

  uint protocol = MYSQL_PROTOCOL_DEFAULT;
  uint ssl_mode = 0;
  if (!mysql_get_option(&mysql_handle, MYSQL_OPT_PROTOCOL, &protocol) &&
      !mysql_get_option(&mysql_handle, MYSQL_OPT_SSL_MODE, &ssl_mode)) {
    if (protocol == MYSQL_PROTOCOL_SOCKET && ssl_mode >= SSL_MODE_REQUIRED)
      put_info(
          "You are enforcing ssl connection via unix socket. Please consider\n"
          "switching ssl off as it does not make connection via unix socket\n"
          "any more secure.",
          INFO_INFO);
  }

  dollar_quote_supported = server_supports_dollar_quote(&mysql_handle);

  status.exit_status = read_and_execute(!status.batch);

  mysql_client_plugin_deinit();

  if (opt_outfile) end_tee();
  mysql_end(0);
  return 0;  // Keep compiler happy
}

void mysql_end(int sig) {
#ifndef _WIN32
  /*
    Ignoring SIGQUIT, SIGINT and SIGHUP signals when cleanup process starts.
    This will help in resolving the double free issues, which occurs in case
    the signal handler function is started in between the clean up function.
  */
  signal(SIGQUIT, SIG_IGN);
  signal(SIGINT, SIG_IGN);
  signal(SIGHUP, SIG_IGN);
#endif

  if (ssl_session_data)
    mysql_free_ssl_session_data(&mysql_handle, ssl_session_data);
  mysql_close(&mysql_handle);
#ifdef HAVE_READLINE
  if (!status.batch && !quick && histfile && histfile[0]) {
    /* write-history */
    if (verbose) tee_fprintf(stdout, "Writing history-file %s\n", histfile);
    if (!write_history(histfile_tmp))
      my_rename(histfile_tmp, histfile, MYF(MY_WME));
  }
  batch_readline_end(status.line_buff);
  completion_hash_free(&ht);
  hash_mem_root.Clear();

  my_free(histfile);
  my_free(histfile_tmp);
#endif
  my_free(opt_histignore);

  my_free(current_os_user);
  my_free(current_os_sudouser);

  if (opt_syslog) my_closelog();

  if (sig >= 0) put_info(sig ? "Aborted" : "Bye", INFO_RESULT);
  glob_buffer.mem_free();
  old_buffer.mem_free();
  processed_prompt.mem_free();
  dollar_quote.mem_free();
  my_free(server_version);
  free_passwords();
  my_free(opt_mysql_unix_port);
  my_free(current_db);
  my_free(current_host);
  my_free(dns_srv_name);
  my_free(current_user);
  my_free(full_username);
  my_free(part_username);
  my_free(default_prompt);
  opt_init_commands.free();
#if defined(_WIN32)
  my_free(shared_memory_base_name);
#endif
  my_free(current_prompt);
  mysql_server_end();
  my_end(my_end_arg);
  if (global_attrs != nullptr) {
    delete global_attrs;
    global_attrs = nullptr;
  }
  if (telemetry_client_attrs != nullptr) {
    delete telemetry_client_attrs;
    telemetry_client_attrs = nullptr;
  }
  exit(status.exit_status);
}

/**
  SIGINT signal handler.

    This function handles SIGINT (Ctrl - C). It sends a 'KILL [QUERY]' command
    to the server if a query is currently executing. On Windows, 'Ctrl - Break'
    is treated alike.

  FIXME: POSIX allows only a very limited set of interactions from signal
  handlers, as the main thread could have nearly any state at the time of the
  signal and is suspended until the signal handler returns. In particular,
  only variables of type sig_atomic_t can be set and tested, and most C library
  functions (including malloc()) are banned. Thus, calling kill_query() here
  is forbidden and should not be done.
*/

void handle_ctrlc_signal(int) {
  sigint_received = true;

  /* Skip rest if --sigint-ignore is used. */
  if (opt_sigint_ignore) return;

  if (executing_query) kill_query("^C");
  /* else, do nothing, just terminate the current line (like /c command). */
}

/**
   Handler to perform a cleanup and quit the program.

     This function would send a 'KILL [QUERY]' command to the server if a
     query is currently executing and then it invokes mysql_thread_end()/
     mysql_end() in order to terminate the mysql client process.

  @param sig              Signal number
*/

void handle_quit_signal(int sig [[maybe_unused]]) {
  const char *reason = "Terminal close";

  if (!executing_query) {
    tee_fprintf(stdout, "%s -- exit!\n", reason);
    goto err;
  }

  kill_query(reason);

err:
#ifdef _WIN32
  /*
   When a signal is raised on Windows, the OS creates a new thread to
   handle the interrupt. Once that thread completes, the main thread
   continues running only to find that it's resources have already been
   free'd when the signal handler called mysql_end().
  */
  mysql_thread_end();
  return;
#else
  mysql_end(sig);
#endif
}

/* Send 'KILL QUERY' command to the server. */
static void kill_query(const char *reason) {
  char kill_buffer[40];
  MYSQL *kill_mysql = nullptr;

  kill_mysql = mysql_init(kill_mysql);
  init_connection_options(kill_mysql);

#ifdef HAVE_SETNS
  if (opt_network_namespace && set_network_namespace(opt_network_namespace)) {
    goto err;
  }
#endif

  MYSQL *ret;
  if (dns_srv_name)
    ret = mysql_real_connect_dns_srv(kill_mysql, dns_srv_name, current_user,
                                     nullptr, "", 0);
  else
    ret = mysql_real_connect(kill_mysql, current_host, current_user, nullptr,
                             "", opt_mysql_port, opt_mysql_unix_port, 0);
  if (!ret) {
#ifdef HAVE_SETNS
    if (opt_network_namespace) (void)restore_original_network_namespace();
#endif
    tee_fprintf(stdout,
                "%s -- Sorry, cannot connect to the server to kill "
                "query, giving up ...\n",
                reason);
    goto err;
  }

#ifdef HAVE_SETNS
  if (opt_network_namespace && restore_original_network_namespace()) goto err;
#endif

  interrupted_query = true;

  /* mysqld < 5 does not understand KILL QUERY, skip to KILL CONNECTION */
  sprintf(kill_buffer, "KILL %s%lu",
          (mysql_get_server_version(&mysql_handle) < 50000) ? "" : "QUERY ",
          mysql_thread_id(&mysql_handle));

  if (verbose)
    tee_fprintf(stdout, "%s -- sending \"%s\" to server ...\n", reason,
                kill_buffer);
  mysql_real_query(kill_mysql, kill_buffer,
                   static_cast<ulong>(strlen(kill_buffer)));
  tee_fprintf(stdout, "%s -- query aborted\n", reason);

err:
#ifdef HAVE_SETNS
  if (opt_network_namespace) (void)release_network_namespace_resources();
#endif
  mysql_close(kill_mysql);
}

#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL)
void window_resize(int) {
  struct winsize window_size;

  if (ioctl(fileno(stdin), TIOCGWINSZ, &window_size) == 0)
    terminal_width = window_size.ws_col;
}
#endif

static struct my_option my_long_options[] = {
    {"help", '?', "Display this help and exit.", nullptr, nullptr, nullptr,
     GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"help", 'I', "Synonym for -?", nullptr, nullptr, nullptr, GET_NO_ARG,
     NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"auto-rehash", OPT_AUTO_REHASH,
     "Enable automatic rehashing. One doesn't need to use 'rehash' to get "
     "table "
     "and field completion, but startup and reconnecting may take a longer "
     "time. "
     "Disable with --disable-auto-rehash.",
     &opt_rehash, &opt_rehash, nullptr, GET_BOOL, NO_ARG, 1, 0, 0, nullptr, 0,
     nullptr},
    {"no-auto-rehash", 'A',
     "No automatic rehashing. One has to use 'rehash' to get table and field "
     "completion. This gives a quicker start of mysql and disables rehashing "
     "on reconnect.",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"auto-vertical-output", OPT_AUTO_VERTICAL_OUTPUT,
     "Automatically switch to vertical output mode if the result is wider "
     "than the terminal width.",
     &auto_vertical_output, &auto_vertical_output, nullptr, GET_BOOL, NO_ARG, 0,
     0, 0, nullptr, 0, nullptr},
    {"batch", 'B',
     "Don't use history file. Disable interactive behavior. (Enables "
     "--silent.)",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"bind-address", 0, "IP address to bind to.", (uchar **)&opt_bind_addr,
     (uchar **)&opt_bind_addr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr,
     0, nullptr},
    {"binary-as-hex", OPT_MYSQL_BINARY_AS_HEX,
     "Print binary data as hex. Enabled by default for interactive terminals.",
     &opt_binhex, &opt_binhex, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"character-sets-dir", OPT_CHARSETS_DIR,
     "Directory for character set files.", &charsets_dir, &charsets_dir,
     nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"column-type-info", OPT_COLUMN_TYPES, "Display column type information.",
     &column_types_flag, &column_types_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"commands", OPT_MYSQL_COMMANDS,
     "Enable or disable processing of local mysql commands.", &opt_commands,
     &opt_commands, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"comments", 'c',
     "Preserve comments. Send comments to the server."
     " The default is --comments (keep comments), disable with "
     "--skip-comments.",
     &preserve_comments, &preserve_comments, nullptr, GET_BOOL, NO_ARG, 1, 0, 0,
     nullptr, 0, nullptr},
    {"compress", 'C', "Use compression in server/client protocol.",
     &opt_compress, &opt_compress, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
     0, nullptr},
#ifdef NDEBUG
    {"debug", '#', "This is a non-debug version. Catch this and exit.", nullptr,
     nullptr, nullptr, GET_DISABLED, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"debug-check", OPT_DEBUG_CHECK,
     "This is a non-debug version. Catch this and exit.", nullptr, nullptr,
     nullptr, GET_DISABLED, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"debug-info", 'T', "This is a non-debug version. Catch this and exit.",
     nullptr, nullptr, nullptr, GET_DISABLED, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
#else
    {"debug", '#', "Output debug log.", &default_dbug_option,
     &default_dbug_option, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"debug-check", OPT_DEBUG_CHECK,
     "Check memory and open file usage at exit.", &debug_check_flag,
     &debug_check_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag,
     &debug_info_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
#endif
    {"database", 'D', "Database to use.", &current_db, &current_db, nullptr,
     GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"default-character-set", OPT_DEFAULT_CHARSET,
     "Set the default character set.", &default_charset, &default_charset,
     nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"delimiter", OPT_DELIMITER, "Delimiter to be used.", &delimiter_str,
     &delimiter_str, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"enable_cleartext_plugin", OPT_ENABLE_CLEARTEXT_PLUGIN,
     "Enable/disable the clear text authentication plugin.",
     &opt_enable_cleartext_plugin, &opt_enable_cleartext_plugin, nullptr,
     GET_BOOL, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"execute", 'e',
     "Execute command and quit. (Disables --force and history file.)", nullptr,
     nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"vertical", 'E', "Print the output of a query (rows) vertically.",
     &vertical, &vertical, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"force", 'f', "Continue even if we get an SQL error.", &ignore_errors,
     &ignore_errors, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"histignore", OPT_HISTIGNORE,
     "A colon-separated list of patterns to "
     "keep statements from getting logged into syslog and mysql history.",
     &opt_histignore, &opt_histignore, nullptr, GET_STR_ALLOC, REQUIRED_ARG, 0,
     0, 0, nullptr, 0, nullptr},
    {"named-commands", 'G',
     "Enable named commands. Named commands mean this program's internal "
     "commands; see mysql> help . When enabled, the named commands can be "
     "used from any line of the query, otherwise only from the first line, "
     "before an enter. Disable with --disable-named-commands. This option "
     "is disabled by default.",
     &named_cmds, &named_cmds, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"ignore-spaces", 'i', "Ignore space after function names.", &ignore_spaces,
     &ignore_spaces, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"init-command", OPT_INIT_COMMAND,
     "Single SQL Command to execute when connecting to MySQL server. Will "
     "automatically be re-executed when reconnecting.",
     nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"init-command-add", OPT_INIT_COMMAND_ADD,
     "Add SQL command to the list to execute when connecting to MySQL server. "
     "Will automatically be re-executed when reconnecting.",
     nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"local-infile", OPT_LOCAL_INFILE, "Enable/disable LOAD DATA LOCAL INFILE.",
     &opt_local_infile, &opt_local_infile, nullptr, GET_BOOL, OPT_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"no-beep", 'b', "Turn off beep on error.", &opt_nobeep, &opt_nobeep,
     nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"host", 'h', "Connect to host.", &current_host, &current_host, nullptr,
     GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"dns-srv-name", 0, "Connect to a DNS SRV resource", &dns_srv_name,
     &dns_srv_name, nullptr, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"html", 'H', "Produce HTML output.", &opt_html, &opt_html, nullptr,
     GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"xml", 'X', "Produce XML output.", &opt_xml, &opt_xml, nullptr, GET_BOOL,
     NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"line-numbers", OPT_LINE_NUMBERS, "Write line numbers for errors.",
     &line_numbers, &line_numbers, nullptr, GET_BOOL, NO_ARG, 1, 0, 0, nullptr,
     0, nullptr},
    {"skip-line-numbers", 'L', "Don't write line number for errors.", nullptr,
     nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"unbuffered", 'n', "Flush buffer after each query.", &unbuffered,
     &unbuffered, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"column-names", OPT_COLUMN_NAMES, "Write column names in results.",
     &column_names, &column_names, nullptr, GET_BOOL, NO_ARG, 1, 0, 0, nullptr,
     0, nullptr},
    {"skip-column-names", 'N', "Don't write column names in results.", nullptr,
     nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"sigint-ignore", OPT_SIGINT_IGNORE, "Ignore SIGINT (CTRL-C).",
     &opt_sigint_ignore, &opt_sigint_ignore, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"one-database", 'o',
     "Ignore statements except those that occur while the default "
     "database is the one named at the command line.",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
#ifdef USE_POPEN
    {"pager", OPT_PAGER,
     "Pager to use to display results. If you don't supply an option, the "
     "default pager is taken from your ENV variable PAGER. Valid pagers are "
     "less, more, cat [> filename], etc. See interactive help (\\h) also. "
     "This option does not work in batch mode. Disable with --disable-pager. "
     "This option is disabled by default.",
     nullptr, nullptr, nullptr, GET_STR, OPT_ARG, 0, 0, 0, nullptr, 0, nullptr},
#endif
#include "client/include/multi_factor_passwordopt-longopts.h"
#ifdef _WIN32
    {"pipe", 'W', "Use named pipes to connect to server.", nullptr, nullptr,
     nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
#endif
    {"port", 'P',
     "Port number to use for connection or 0 for default to, in "
     "order of preference, my.cnf, $MYSQL_TCP_PORT, "
#if MYSQL_PORT_DEFAULT == 0
     "/etc/services, "
#endif
     "built-in default (" STRINGIFY_ARG(MYSQL_PORT) ").",
     &opt_mysql_port, &opt_mysql_port, nullptr, GET_UINT, REQUIRED_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"prompt", OPT_PROMPT, "Set the mysql prompt to this value.",
     &current_prompt, &current_prompt, nullptr, GET_STR_ALLOC, REQUIRED_ARG, 0,
     0, 0, nullptr, 0, nullptr},
    {"protocol", OPT_MYSQL_PROTOCOL,
     "The protocol to use for connection (tcp, socket, pipe, memory).", nullptr,
     nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"quick", 'q',
     "Don't cache result, print it row by row. This may slow down the server "
     "if the output is suspended. Doesn't use history file.",
     &quick, &quick, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"raw", 'r', "Write fields without conversion. Used with --batch.",
     &opt_raw_data, &opt_raw_data, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
     0, nullptr},
    {"reconnect", OPT_RECONNECT,
     "Reconnect if the connection is lost. Disable "
     "with --disable-reconnect. This option is enabled by default.",
     &opt_reconnect, &opt_reconnect, nullptr, GET_BOOL, NO_ARG, 1, 0, 0,
     nullptr, 0, nullptr},
    {"silent", 's',
     "Be more silent. Print results with a tab as separator, "
     "each row on new line.",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
#if defined(_WIN32)
    {"shared-memory-base-name", OPT_SHARED_MEMORY_BASE_NAME,
     "Base name of shared memory.", &shared_memory_base_name,
     &shared_memory_base_name, nullptr, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
#endif
    {"socket", 'S', "The socket file to use for connection.",
     &opt_mysql_unix_port, &opt_mysql_unix_port, nullptr, GET_STR_ALLOC,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
#include "client/include/caching_sha2_passwordopt-longopts.h"
#include "client/include/sslopt-longopts.h"

    {"table", 't', "Output in table format.", &output_tables, &output_tables,
     nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"tee", OPT_TEE,
     "Append everything into outfile. See interactive help (\\h) also. "
     "Does not work in batch mode. Disable with --disable-tee. "
     "This option is disabled by default.",
     nullptr, nullptr, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"user", 'u', "User for login if not current user.", &current_user,
     &current_user, nullptr, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"safe-updates", 'U', "Only allow UPDATE and DELETE that uses keys.",
     &safe_updates, &safe_updates, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
     0, nullptr},
    {"i-am-a-dummy", 'U', "Synonym for option --safe-updates, -U.",
     &safe_updates, &safe_updates, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr,
     0, nullptr},
    {"verbose", 'v', "Write more. (-v -v -v gives the table output format).",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"version", 'V', "Output version information and exit.", nullptr, nullptr,
     nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"wait", 'w', "Wait and retry if connection is down.", &wait_flag,
     &wait_flag, nullptr, GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"connect_timeout", OPT_CONNECT_TIMEOUT,
     "Number of seconds before connection timeout.", &opt_connect_timeout,
     &opt_connect_timeout, nullptr, GET_ULONG, REQUIRED_ARG, 0, 0, 3600 * 12,
     nullptr, 0, nullptr},
    {"max_allowed_packet", OPT_MAX_ALLOWED_PACKET,
     "The maximum packet length to send to or receive from server.",
     &opt_max_allowed_packet, &opt_max_allowed_packet, nullptr, GET_ULONG,
     REQUIRED_ARG, 16 * 1024L * 1024L, 4096,
     (longlong)2 * 1024L * 1024L * 1024L, nullptr, 1024, nullptr},
    {"net_buffer_length", OPT_NET_BUFFER_LENGTH,
     "The buffer size for TCP/IP and socket communication.",
     &opt_net_buffer_length, &opt_net_buffer_length, nullptr, GET_ULONG,
     REQUIRED_ARG, 16384, 1024, 512 * 1024 * 1024L, nullptr, 1024, nullptr},
    {"select_limit", OPT_SELECT_LIMIT,
     "Automatic limit for SELECT when using --safe-updates.", &select_limit,
     &select_limit, nullptr, GET_ULONG, REQUIRED_ARG, 1000L, 1, ULONG_MAX,
     nullptr, 1, nullptr},
    {"max_join_size", OPT_MAX_JOIN_SIZE,
     "Automatic limit for rows in a join when using --safe-updates.",
     &max_join_size, &max_join_size, nullptr, GET_ULONG, REQUIRED_ARG, 1000000L,
     1, ULONG_MAX, nullptr, 1, nullptr},
    {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.",
     &show_warnings, &show_warnings, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"syslog", 'j',
     "Log filtered interactive commands to syslog. Filtering of "
     "commands depends on the patterns supplied via histignore option besides "
     "the default patterns.",
     nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.",
     &opt_plugin_dir, &opt_plugin_dir, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"default_auth", OPT_DEFAULT_AUTH,
     "Default authentication client-side plugin to use.", &opt_default_auth,
     &opt_default_auth, nullptr, GET_STR, REQUIRED_ARG, 0, 0, 0, nullptr, 0,
     nullptr},
    {"binary-mode", OPT_BINARY_MODE,
     "By default, ASCII '\\0' is disallowed and '\\r\\n' is translated to "
     "'\\n'. "
     "This switch turns off both features, and also turns off parsing of all "
     "client"
     "commands except \\C and DELIMITER, in non-interactive mode (for input "
     "piped to mysql or loaded using the 'source' command). This is necessary "
     "when processing output from mysqlbinlog that may contain blobs.",
     &opt_binary_mode, &opt_binary_mode, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
    {"connect-expired-password", 0,
     "Notify the server that this client is prepared to handle expired "
     "password sandbox mode.",
     &opt_connect_expired_password, &opt_connect_expired_password, nullptr,
     GET_BOOL, NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
#ifndef NDEBUG
    {"build-completion-hash", 0,
     "Build completion hash even when it is in batch mode. It is used for "
     "test purpose, so it is just built when DEBUG is on.",
     &opt_build_completion_hash, &opt_build_completion_hash, nullptr, GET_BOOL,
     NO_ARG, 0, 0, 0, nullptr, 0, nullptr},
#endif
#ifdef HAVE_SETNS
    {"network-namespace", 0,
     "Network namespace to use for connection via tcp with a server.",
     &opt_network_namespace, &opt_network_namespace, nullptr, GET_STR,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
#endif
    {"compression-algorithms", 0,
     "Use compression algorithm in server/client protocol. Valid values "
     "are any combination of 'zstd','zlib','uncompressed'.",
     &opt_compress_algorithm, &opt_compress_algorithm, nullptr, GET_STR,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"zstd-compression-level", 0,
     "Use this compression level in the client/server protocol, in case "
     "--compression-algorithms=zstd. Valid range is between 1 and 22, "
     "inclusive. Default is 3.",
     &opt_zstd_compress_level, &opt_zstd_compress_level, nullptr, GET_UINT,
     REQUIRED_ARG, 3, 1, 22, nullptr, 0, nullptr},
    {"load_data_local_dir", OPT_LOAD_DATA_LOCAL_DIR,
     "Directory path safe for LOAD DATA LOCAL INFILE to read from.",
     &opt_load_data_local_dir, &opt_load_data_local_dir, nullptr, GET_STR,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"authentication-oci-client-config-profile", 0,
     "Specifies the configuration profile whose configuration options are to "
     "be read from the OCI configuration file. Default is DEFAULT.",
     &opt_authentication_oci_client_config_profile,
     &opt_authentication_oci_client_config_profile, nullptr, GET_STR,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"oci-config-file", 0,
     "Specifies the location of the OCI configuration file. Default for Linux "
     "is ~/.oci/config and %HOME/.oci/config on Windows.",
     &opt_oci_config_file, &opt_oci_config_file, nullptr, GET_STR, REQUIRED_ARG,
     0, 0, 0, nullptr, 0, nullptr},
    {"authentication-openid-connect-client-id-token-file", 0,
     "Specifies the location of the ID token file.",
     &opt_authentication_openid_connect_client_id_token_file,
     &opt_authentication_openid_connect_client_id_token_file, nullptr, GET_STR,
     REQUIRED_ARG, 0, 0, 0, nullptr, 0, nullptr},
    {"telemetry-client", 0, "Load the telemetry_client plugin.",
     &opt_tel_plugin, &opt_tel_plugin, nullptr, GET_BOOL, NO_ARG, 0, 0, 0,
     nullptr, 0, nullptr},
#include "client/include/authentication_kerberos_clientopt-longopts.h"
#include "client/include/authentication_webauthn_clientopt-longopts.h"
    {"register-factor", 0,
     "Specifies factor for which registration needs to be done for.",
     &opt_register_factor, &opt_register_factor, nullptr, GET_STR, REQUIRED_ARG,
     0, 0, 0, nullptr, 0, nullptr},
    {"system-command", 0,
     "Enable or disable (by default) the 'system' mysql command.",
     &opt_system_command, &opt_system_command, nullptr, GET_BOOL, NO_ARG, 0, 0,
     0, nullptr, 0, nullptr},
    {nullptr, 0, nullptr, nullptr, nullptr, nullptr, GET_NO_ARG, NO_ARG, 0, 0,
     0, nullptr, 0, nullptr}};

static void usage(int version) {
  print_version();

  if (version) return;
  puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"));
  printf("Usage: %s [OPTIONS] [database]\n", my_progname);
  my_print_help(my_long_options);
  print_defaults("my", load_default_groups);
  my_print_variables(my_long_options);
}

bool get_one_option(int optid, const struct my_option *opt [[maybe_unused]],
                    char *argument) {
  switch (optid) {
    case OPT_CHARSETS_DIR:
      strmake(mysql_charsets_dir, argument, sizeof(mysql_charsets_dir) - 1);
      charsets_dir = mysql_charsets_dir;
      break;
    case OPT_DELIMITER:
      if (argument == disabled_my_option) {
        my_stpcpy(delimiter, DEFAULT_DELIMITER);
      } else {
        /* Check that delimiter does not contain a backslash */
        if (!strstr(argument, "\\")) {
          strmake(delimiter, argument, sizeof(delimiter) - 1);
        } else {
          put_info("DELIMITER cannot contain a backslash character",
                   INFO_ERROR);
          return false;
        }
      }
      delimiter_length = (uint)strlen(delimiter);
      delimiter_str = delimiter;
      break;
    case OPT_LOCAL_INFILE:
      using_opt_local_infile = true;
      break;
    case OPT_ENABLE_CLEARTEXT_PLUGIN:
      using_opt_enable_cleartext_plugin = true;
      break;
    case OPT_TEE:
      if (argument == disabled_my_option) {
        if (opt_outfile) end_tee();
      } else
        init_tee(argument);
      break;
    case OPT_PAGER:
      if (argument == disabled_my_option)
        opt_nopager = true;
      else {
        opt_nopager = false;
        if (argument && strlen(argument)) {
          default_pager_set = true;
          strmake(pager, argument, sizeof(pager) - 1);
          my_stpcpy(default_pager, pager);
        } else if (default_pager_set)
          my_stpcpy(pager, default_pager);
        else
          opt_nopager = true;
      }
      break;
    case OPT_MYSQL_PROTOCOL:
      opt_protocol =
          find_type_or_exit(argument, &sql_protocol_typelib, opt->name);
      break;
    case 'A':
      opt_rehash = false;
      break;
    case 'N':
      column_names = false;
      break;
    case 'e':
      status.batch = true;
      status.add_to_history = false;
      if (!status.line_buff)
        ignore_errors = false;  // do it for the first -e only
      status.line_buff = batch_readline_command(status.line_buff, argument);
      if (!status.line_buff) return true;
      break;
    case 'j':
      if (my_openlog("MysqlClient", 0, LOG_USER)) {
        /* error */
        put_info(strerror(errno), INFO_ERROR, errno);
        return true;
      }
      opt_syslog = true;
      break;
    case 'o':
      if (argument == disabled_my_option)
        one_database = false;
      else
        one_database = skip_updates = true;
      break;
      PARSE_COMMAND_LINE_PASSWORD_OPTION;
    case '#':
      DBUG_PUSH(argument ? argument : default_dbug_option);
      debug_info_flag = true;
      break;
    case 's':
      if (argument == disabled_my_option)
        opt_silent = 0;
      else
        opt_silent++;
      break;
    case 'v':
      if (argument == disabled_my_option)
        verbose = 0;
      else
        verbose++;
      break;
    case 'B':
      status.batch = true;
      status.add_to_history = false;
      opt_silent = std::max(opt_silent, 1U);  // more silent
      break;
    case 'W':
#ifdef _WIN32
      opt_protocol = MYSQL_PROTOCOL_PIPE;
#endif
      break;
#include "client/include/sslopt-case.h"

#include "client/include/authentication_kerberos_clientopt-case.h"
#include "client/include/authentication_webauthn_clientopt-case.h"

    case 'V':
      usage(1);
      exit(0);
    case 'I':
    case '?':
      usage(0);
      exit(0);
    case OPT_MYSQL_BINARY_AS_HEX:
      opt_binhex = (argument != disabled_my_option);
      opt_binary_as_hex_set_explicitly = true;
      break;
    case 'C':
      CLIENT_WARN_DEPRECATED("--compress", "--compression-algorithms");
      break;
    case OPT_INIT_COMMAND:
      opt_init_commands.add_value(argument, true);
      break;
    case OPT_INIT_COMMAND_ADD:
      opt_init_commands.add_value(argument, false);
      break;
  }
  return false;
}

static int get_options(int *argc_ptr, char ***argv_ptr) {
  char *tmp, *pagpoint;
  int ho_error;

  tmp = (char *)getenv("MYSQL_HOST");
  if (tmp) current_host = my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME));

  pagpoint = getenv("PAGER");
  if (!((char *)(pagpoint))) {
    my_stpcpy(pager, "stdout");
    opt_nopager = true;
  } else
    my_stpcpy(pager, pagpoint);
  my_stpcpy(default_pager, pager);

  if (mysql_get_option(nullptr, MYSQL_OPT_MAX_ALLOWED_PACKET,
                       &opt_max_allowed_packet) ||
      mysql_get_option(nullptr, MYSQL_OPT_NET_BUFFER_LENGTH,
                       &opt_max_allowed_packet)) {
    exit(1);
  }

  /*
    We may have options for the telemetry client plugin, to consume later.
  */
  my_getopt_skip_unknown = true;

  ho_error =
      handle_options(argc_ptr, argv_ptr, my_long_options, get_one_option);
  if (ho_error) {
    exit(ho_error);
  }

  if (mysql_options(nullptr, MYSQL_OPT_MAX_ALLOWED_PACKET,
                    &opt_max_allowed_packet) ||
      mysql_options(nullptr, MYSQL_OPT_NET_BUFFER_LENGTH,
                    &opt_net_buffer_length)) {
    exit(1);
  }

  if (status.batch) /* disable pager and outfile in this case */
  {
    my_stpcpy(default_pager, "stdout");
    my_stpcpy(pager, "stdout");
    opt_nopager = true;
    default_pager_set = false;
    opt_outfile = false;
    opt_reconnect = false;
    connect_flag = 0; /* Not in interactive mode */
  }

  if (debug_info_flag) my_end_arg = MY_CHECK_ERROR | MY_GIVE_INFO;
  if (debug_check_flag) my_end_arg = MY_CHECK_ERROR;

  if (ignore_spaces) connect_flag |= CLIENT_IGNORE_SPACE;

  return (0);
}

static int read_and_execute(bool interactive) {
#if defined(_WIN32)
  String tmpbuf;
  String buffer;
#endif

  /*
    line can be allocated by:
    - batch_readline. Use my_free()
    - my_win_console_readline. Do not free, see tmpbuf.
    - readline. Use free()
  */
  char *line = nullptr;
  char in_string = 0;
  ulong line_number = 0;
  bool ml_comment = false;
  COMMANDS *com;
  size_t line_length = 0;
  status.exit_status = 1;

  disable_commands = !interactive && (opt_binary_mode || !opt_commands);
  for (;;) {
    /* Reset as SIGINT has already got handled. */
    sigint_received = false;

    if (!interactive) {
      /*
        batch_readline can return 0 on EOF or error.
        In that case, we need to double check that we have a valid
        line before actually setting line_length to read_length.
        */
      line = batch_readline(status.line_buff, opt_binary_mode);
      if (line) {
        line_length = status.line_buff->read_length;

        /*
          ASCII 0x00 is not allowed appearing in queries if it is not in
          binary mode.
        */
        if (!opt_binary_mode && strlen(line) != line_length) {
          status.exit_status = 1;
          String msg;
          msg.append(
              "ASCII '\\0' appeared in the statement, but this is not "
              "allowed unless option --binary-mode is enabled and mysql is "
              "run in non-interactive mode. Set --binary-mode to 1 if ASCII "
              "'\\0' is expected. Query: '");
          msg.append(glob_buffer);
          msg.append(line);
          msg.append("'.");
          put_info(msg.c_ptr(), INFO_ERROR);
          break;
        }

        /*
          Skip UTF8 Byte Order Marker (BOM) 0xEFBBBF.
          Editors like "notepad" put this marker in
          the very beginning of a text file when
          you save the file using "Unicode UTF-8" format.
        */
        if (!line_number && (uchar)line[0] == 0xEF && (uchar)line[1] == 0xBB &&
            (uchar)line[2] == 0xBF) {
          line += 3;
          // decrease the line length accordingly to the 3 bytes chopped
          line_length -= 3;
        }
      }
      line_number++;
      if (!glob_buffer.length()) status.query_start_line = line_number;
    } else {
      const char *prompt;
      if (ml_comment) {
        prompt = "   /*> ";
      } else if (glob_buffer.is_empty()) {
        prompt = construct_prompt();
      } else {
        switch (in_string) {
          case 0:
            prompt = "    -> ";
            break;
          case '\'':
            prompt = "    '> ";
            break;
          case '`':
            prompt = "    `> ";
            break;
          case '"':
            prompt = "    \"> ";
            break;
          case '$':
            prompt = "    $> ";
            break;
          default:
            assert(false);
            prompt = "    -> ";
        }
      }

      if (opt_outfile && glob_buffer.is_empty()) fflush(OUTFILE);

#if defined(_WIN32)
      size_t nread;
      tee_fputs(prompt, stdout);
      if (!tmpbuf.is_alloced()) tmpbuf.alloc(65535);
      tmpbuf.length(0);
      buffer.length(0);
      line = my_win_console_readline(charset_info, (char *)tmpbuf.ptr(),
                                     tmpbuf.alloced_length(), &nread);
      if (line && (nread == 0)) {
        tee_puts("^C", stdout);
        reset_prompt(&in_string, &ml_comment);
        continue;
      } else if (*line == 0x1A) /* (Ctrl + Z) */
        break;
#else
      if (opt_outfile) fputs(prompt, OUTFILE);
      /*
        free the previous entered line.
      */
      if (line) free(line);
      line = readline(prompt);

      if (sigint_received) {
        sigint_received = false;
        tee_puts("^C", stdout);
        reset_prompt(&in_string, &ml_comment);
        continue;
      }
#endif /* defined(_WIN32) */
      /*
        When Ctrl+d or Ctrl+z is pressed, the line may be NULL on some OS
        which may cause coredump.
      */
      if (opt_outfile && line) fprintf(OUTFILE, "%s\n", line);

      line_length = line ? strlen(line) : 0;
    }
    // End of file or system error
    if (!line) {
      if (status.line_buff && status.line_buff->error)
        status.exit_status = 1;
      else
        status.exit_status = 0;
      break;
    }

    char *skip_comments_start = nullptr;
    /*
      Check if line is a mysql command line
      We want to allow help, print and clear anywhere at line start,
      or when the preceeding data in the buffer are comments only.
      A line is a mysql command line if we're not in a ML comment or a string
      and:
       * the nested_commands is on
       * or it's the first line in a multi-line statement
       * or the previous lines in multi-line statements are comments and
          whitespace and preserve_comments is on
    */
    if ((named_cmds || glob_buffer.is_empty() ||
         (!glob_buffer.is_empty() &&
          ((skip_comments_start = skip_over_comments_and_space(
                glob_buffer.ptr(), glob_buffer.length())) -
           glob_buffer.ptr()) >= (off_t)glob_buffer.length())) &&
        !ml_comment && !in_string) {
      /* The line itself might start with a comment. Skip that too */
      char *line_past_comments =
          skip_over_comments_and_space(line, strlen(line));
      com = find_command(line_past_comments);
      if (com) {
        /*
          The command buffer contained just comments and whitespace and we've
          found a mysql command on the current line. It's OK to empty the
          buffer.
        */
        if (skip_comments_start) glob_buffer.length(0);
        if ((*com->func)(&glob_buffer, line_past_comments) > 0) {
          // lets log the exit/quit command.
          if (interactive && status.add_to_history && com->cmd_char == 'q')
            add_filtered_history(line);
          break;
        }
        if (glob_buffer.is_empty())  // If buffer was emptied
          in_string = 0;
        if (interactive && status.add_to_history) add_filtered_history(line);
        continue;
      }
    }
    if (add_line(glob_buffer, line, line_length, &in_string, &ml_comment,
                 status.line_buff ? status.line_buff->truncated : false))
      break;
  }
  /* if in batch mode, send last query even if it doesn't end with \g or go */

  if (!interactive && !status.exit_status) {
    remove_cntrl(&glob_buffer);
    if (!glob_buffer.is_empty()) {
      status.exit_status = 1;
      char *skip_comments_start =
          skip_over_comments_and_space(glob_buffer.ptr(), glob_buffer.length());
      com = find_command(skip_comments_start);
      if (com) {
        trim_leading_comments_and_space(&glob_buffer, skip_comments_start);
        if ((*com->func)(&glob_buffer, glob_buffer.c_ptr()) > 0)
          status.exit_status = 0;
      } else if (com_go(&glob_buffer, nullptr) <= 0)
        status.exit_status = 0;
    }
  }

#if defined(_WIN32)
  buffer.mem_free();
  tmpbuf.mem_free();
#else
  if (interactive)
    /*
      free the last entered line.
    */
    free(line);
#endif

  /*
    If the function is called by 'source' command, it will return to
    interactive mode, so disable_commands should be false. Otherwise, it will
    exit the program, it is safe to set disable_commands to false.
  */
  disable_commands = false;
  return status.exit_status;
}

static inline void reset_prompt(char *in_string, bool *ml_comment) {
  glob_buffer.length(0);
  *ml_comment = false;
  *in_string = 0;
}

/**
   It checks if the input is a short form command. It returns the command's
   pointer if a command is found, else return NULL. Note that if binary-mode
   is set, then only @\C is searched for.

   @param cmd_char    A character of one byte.

   @return
     the command's pointer or NULL.
*/
static COMMANDS *find_command(char cmd_char) {
  DBUG_TRACE;
  DBUG_PRINT("enter", ("cmd_char: %d", cmd_char));

  int index = -1;

  /*
    In binary-mode, we disallow all mysql commands except '\C'
    and DELIMITER.
  */
  if (disable_commands) {
    if (cmd_char == 'C') index = charset_index;
  } else
    index = get_command_index(cmd_char);

  if (index >= 0) {
    DBUG_PRINT("exit", ("found command: %s", commands[index].name));
    return &commands[index];
  }
  return (COMMANDS *)nullptr;
}

/**
 Skips over possible multi-line comments (/ * and * /) and whitespace
 and finds the first non-comment command.

 We only have to worry about multi-line comments since the single line
 comments (//foo) are sent immediately to the server anyway

 @param ptr Pointer to the start of the command
 @param length The length of the command text in ptr
 @return the first non-comment and non-whitespace command.

 @sa @ref trim_leading_comments_and_space
*/
static char *skip_over_comments_and_space(char *ptr, size_t length) {
  if (!preserve_comments) return ptr;
  char *end = ptr + length;
  do {
    while (ptr < end && my_isspace(charset_info, *ptr)) ptr++;
    if (ptr + 2 < end && *ptr == '/' && ptr[1] == '*') {
      /* skip a multiline comment */
      /* skip past the opening / and * */
      ptr += 2;
      while (ptr + 2 < end && *ptr != '*' && ptr[1] != '/') ptr++;
      /* if we found a closing * and / skip past it */
      if (ptr + 2 < end && *ptr == '*' && ptr[1] == '/') ptr += 2;
    }
  } while (ptr < end && (my_isspace(charset_info, *ptr) ||
                         (ptr + 2 < end && *ptr == '/' && ptr[1] == '*')));
  return ptr;
}

/**
  Trims the leading comments and whitespace from a command

  Useful when @ref skip_over_comments_and_space is used to find if there is
  something to skip and now it needs to be skipped.
  Note that the the function expects that skip_comments_start is within
  buffer.

  @note The idea is that we first look skip non-destructively over the comments
  and space and then check if it's a MySQL command or not. If it is we then trip
  the leading comments and space and proceed. If not we send the comments and
  the command to the server.

  @param [in,out] buffer The string buffer containing the command + the
  comments/whitespace
  @param skip_comments_start The place where leading comments end and the actual
  command starts
*/
static void trim_leading_comments_and_space(String *buffer,
                                            const char *skip_comments_start) {
  if (!preserve_comments) return;
  String str;

  assert(buffer->c_ptr() <= skip_comments_start);
  assert(buffer->c_ptr() + buffer->length() > skip_comments_start);

  if (skip_comments_start != buffer->c_ptr()) {
    str = buffer->substr(
        skip_comments_start - buffer->c_ptr(),
        buffer->length() - (skip_comments_start - buffer->c_ptr()));
    if (str.uses_buffer_owned_by(buffer)) str.copy();
    buffer->takeover(str);
  }
}

/**
   It checks if the input is a long form command. It returns the command's
   pointer if a command is found, else return NULL. Note that if binary-mode
   is set, then only DELIMITER is searched for.

   @param name    A string.
   @return
     the command's pointer or NULL.
*/
static COMMANDS *find_command(char *name) {
  uint len;
  char *end;
  DBUG_TRACE;

  assert(name != nullptr);
  DBUG_PRINT("enter", ("name: '%s'", name));

  while (my_isspace(charset_info, *name)) name++;
  /*
    If there is an \\g in the row or if the row has a delimiter but
    this is not a delimiter command, let add_line() take care of
    parsing the row and calling find_command().
  */
  if ((!disable_commands && strstr(name, "\\g")) ||
      (strstr(name, delimiter) &&
       !is_delimiter_command(name, DELIMITER_NAME_LEN)))
    return (COMMANDS *)nullptr;

  end = strcont(name, " \t");
  if (end) {
    len = (uint)(end - name);
    while (my_isspace(charset_info, *end)) end++;
    if (!*end) end = nullptr;  // no arguments to function
  } else
    len = (uint)strlen(name);

  int index = -1;
  if (disable_commands) {
    if (is_delimiter_command(name, len)) index = delimiter_index;
  } else {
    /*
      All commands are in the first part of commands array and have a function
      to implement it.
    */
    for (uint i = 0; commands[i].func; i++) {
      if (!my_strnncoll(&my_charset_latin1, (uchar *)name, len,
                        pointer_cast<const uchar *>(commands[i].name), len) &&
          (commands[i].name[len] == '\0') &&
          (!end || commands[i].takes_params)) {
        index = i;
        break;
      }
    }
  }

  if (index >= 0) {
    DBUG_PRINT("exit", ("found command: %s", commands[index].name));
    return &commands[index];
  }
  return (COMMANDS *)nullptr;
}

/**
   Check if there is a dollar quote at current position.
   @param pos                Current position of input line
   @param last_end_of_mb_pos Last byte of previous multi-byte character.
                             Used to check if preceeding char is an mb char.
   @param start_of_line      Start of input line
   @param end_of_line        End of input line
   @return length of dollar quote, 0 if not found
*/
static size_t check_for_dollar_quote(const char *pos,
                                     const char *last_end_of_mb_pos,
                                     const char *start_of_line,
                                     const char *end_of_line) {
  assert(*pos == '$');
  if (!dollar_quote_supported) return 0;

  // Character set state maps should always be initialized
  assert(charset_info->ident_map);
  // (We need info on valid characters in identifiers to ignore
  // the case when the dollar sign is inside an identifier
  const uchar *ident_map = charset_info->ident_map;
  if (pos > start_of_line && (last_end_of_mb_pos == pos - 1 ||
                              ident_map[static_cast<uchar>(*(pos - 1))]))
    return 0;

  // $ is first char of token
  const char *p = pos + 1;
  while (*p != '$' && ident_map[static_cast<uchar>(*p)] && p < end_of_line) {
    int l;
    if (use_mb(charset_info) &&
        (l = my_ismbchar(charset_info, p, end_of_line)) > 1) {
      p += l - 1;
    }
    ++p;
  }
  if (*p != '$') return 0;

  // We have found the start of a dollar quote
  const size_t length = p - pos + 1;
  dollar_quote.copy(pos, length, charset_info);
  return length;
}

static bool add_line(String &buffer, char *line, size_t line_length,
                     char *in_string, bool *ml_comment, bool truncated) {
  uchar inchar;
  char buff[80], *pos, *out;
  COMMANDS *com;
  bool need_space = false;
  enum { SSC_NONE = 0, SSC_CONDITIONAL, SSC_HINT } ss_comment = SSC_NONE;
  DBUG_TRACE;

  if (!line[0] && buffer.is_empty()) return false;

  if (status.add_to_history && line[0]) add_filtered_history(line);

  char *end_of_line = line + line_length;

  char *last_end_of_mb_pos = nullptr;
  for (pos = out = line; pos < end_of_line; pos++) {
    inchar = (uchar)*pos;
    if (!preserve_comments) {
      // Skip spaces at the beginning of a statement
      if (my_isspace(charset_info, inchar) && (out == line) &&
          buffer.is_empty())
        continue;
    }
    // Accept multi-byte characters as-is
    int length;
    if (use_mb(charset_info) &&
        (length = my_ismbchar(charset_info, pos, end_of_line))) {
      if (!*ml_comment || preserve_comments) {
        while (length--) *out++ = *pos++;
        pos--;
      } else
        pos += length - 1;
      last_end_of_mb_pos = pos;
      continue;
    }
    if (!*ml_comment && inchar == '\\' &&
        !(*in_string &&
          (mysql_handle.server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES))) {
      // Found possbile one character command like \c

      inchar = (uchar) * ++pos;
      if (!inchar) break;               // readline adds one '\'
      if (*in_string || inchar == 'N')  // \N is short for NULL
      {                                 // Don't allow commands in string
        *out++ = '\\';
        if ((inchar == '`') && (*in_string == inchar))
          pos--;
        else
          *out++ = (char)inchar;
        continue;
      }
      com = find_command((char)inchar);
      if (com) {
        // Flush previously accepted characters
        if (out != line) {
          buffer.append(line, (uint)(out - line));
          out = line;
        }

        if ((*com->func)(&buffer, pos - 1) > 0) return true;  // Quit
        if (com->takes_params) {
          if (ss_comment) {
            /*
              If a client-side macro appears inside a server-side comment,
              discard all characters in the comment after the macro (that is,
              until the end of the comment rather than the next delimiter)
            */
            for (pos++; *pos && (*pos != '*' || *(pos + 1) != '/'); pos++)
              ;
            pos--;
          } else {
            for (pos++; *pos && (*pos != *delimiter ||
                                 !is_prefix(pos + 1, delimiter + 1));
                 pos++)
              ;  // Remove parameters
            if (!*pos)
              pos--;
            else
              pos += delimiter_length - 1;  // Point at last delim char
          }
        }
      } else {
        sprintf(buff, "Unknown command '\\%c'.", inchar);
        if (put_info(buff, INFO_ERROR) > 0) return true;
        *out++ = '\\';
        *out++ = (char)inchar;
        continue;
      }
    } else if (!*ml_comment && !*in_string && ss_comment != SSC_HINT &&
               is_prefix(pos, delimiter)) {
      // Found a statement. Continue parsing after the delimiter
      pos += delimiter_length;

      if (preserve_comments) {
        while (my_isspace(charset_info, *pos)) *out++ = *pos++;
      }
      // Flush previously accepted characters
      if (out != line) {
        buffer.append(line, (uint32)(out - line));
        out = line;
      }

      if (preserve_comments &&
          ((*pos == '#') || ((*pos == '-') && (pos[1] == '-') &&
                             my_isspace(charset_info, pos[2])))) {
        // Add trailing single line comments to this statement
        buffer.append(pos);
        pos += strlen(pos);
      }

      pos--;

      char *skip_comments_start = nullptr;
      if (buffer.length()) {
        skip_comments_start =
            skip_over_comments_and_space(buffer.ptr(), buffer.length());
        com = find_command(skip_comments_start);
      } else
        com = nullptr;
      if (nullptr != com) {
        trim_leading_comments_and_space(&buffer, skip_comments_start);
        if ((*com->func)(&buffer, buffer.c_ptr()) > 0) return true;  // Quit
      } else {
        if (com_go(&buffer, nullptr) > 0)  // < 0 is not fatal
          return true;
      }
      buffer.length(0);
    } else if (!*ml_comment && ss_comment != SSC_HINT &&
               (!*in_string &&
                (inchar == '#' ||
                 (inchar == '-' && pos[1] == '-' &&
                  /*
                    The third byte is either whitespace or is the
                    end of the line -- which would occur only
                    because of the user sending newline -- which is
                    itself whitespace and should also match.
                  */
                  (my_isspace(charset_info, pos[2]) || !pos[2]))))) {
      // Flush previously accepted characters
      if (out != line) {
        buffer.append(line, (uint32)(out - line));
        out = line;
      }

      // comment to end of line
      if (preserve_comments) {
        const bool started_with_nothing = !buffer.length();

        buffer.append(pos);

        /*
          A single-line comment by itself gets sent immediately so that
          client commands (delimiter, status, etc) will be interpreted on
          the next line.
        */
        if (started_with_nothing) {
          if (com_go(&buffer, nullptr) > 0)  // < 0 is not fatal
            return true;
          buffer.length(0);
        }
      }

      break;
    } else if (!*in_string && inchar == '/' && pos[1] == '*' && pos[2] != '!' &&
               pos[2] != '+' && ss_comment != SSC_HINT) {
      if (preserve_comments) {
        *out++ = *pos++;  // copy '/'
        *out++ = *pos;    // copy '*'
      } else
        pos++;
      *ml_comment = true;
      if (out != line) {
        buffer.append(line, (uint)(out - line));
        out = line;
      }
    } else if (*ml_comment && !ss_comment && inchar == '*' &&
               *(pos + 1) == '/') {
      if (preserve_comments) {
        *out++ = *pos++;  // copy '*'
        *out++ = *pos;    // copy '/'
      } else
        pos++;
      *ml_comment = false;
      if (out != line) {
        buffer.append(line, (uint32)(out - line));
        out = line;
      }
      // Consumed a 2 chars or more, and will add 1 at most,
      // so using the 'line' buffer to edit data in place is ok.
      need_space = true;
    } else {  // Add found char to buffer
      if (!*in_string && inchar == '/' && pos[1] == '*') {
        if (pos[2] == '!')
          ss_comment = SSC_CONDITIONAL;
        else if (pos[2] == '+')
          ss_comment = SSC_HINT;
      } else if (!*in_string && ss_comment && inchar == '*' &&
                 *(pos + 1) == '/')
        ss_comment = SSC_NONE;
      if (inchar == *in_string) {
        if (inchar != '$') {
          *in_string = 0;
        } else {
          // Check if this is the end of the dollar quoted string
          const size_t len = dollar_quote.length();
          if (strncmp(pos, dollar_quote.c_ptr(), len) == 0) {
            *in_string = 0;
            // Last character will be copied at end of loop
            for (size_t i = 0; i < len - 1; ++i) {
              *out++ = *pos++;
            }
          }
        }
      } else if (!*ml_comment && !*in_string && ss_comment != SSC_HINT) {
        if (inchar == '\'' || inchar == '"' || inchar == '`')
          *in_string = (char)inchar;
        else if (inchar == '$') {
          const size_t len = check_for_dollar_quote(pos, last_end_of_mb_pos,
                                                    line, end_of_line);
          if (len > 0) {
            *in_string = '$';
            for (size_t i = 0; i < len - 1; ++i) {
              *out++ = *pos++;
            }
          }
        }
      }
      if (!*ml_comment || preserve_comments) {
        if (need_space && !my_isspace(charset_info, (char)inchar)) *out++ = ' ';
        need_space = false;
        *out++ = (char)inchar;
      }
    }
  }
  if (out != line || !buffer.is_empty()) {
    uint length = (uint)(out - line);

    if (!truncated &&
        (!is_delimiter_command(line, length) || (*in_string || *ml_comment))) {
      /*
        Don't add a new line in case there's a DELIMITER command to be
        added to the glob buffer (e.g. on processing a line like
        "<command>;DELIMITER <non-eof>") : similar to how a new line is
        not added in the case when the DELIMITER is the first command
        entered with an empty glob buffer. However, if the delimiter is
        part of a string or a comment, the new line should be added. (e.g.
        SELECT '\ndelimiter\n';\n)
      */
      *out++ = '\n';
      length++;
    }
    if (buffer.length() + length >= buffer.alloced_length())
      buffer.mem_realloc(buffer.length() + length + batch_io_size);
    if ((!*ml_comment || preserve_comments) && buffer.append(line, length))
      return true;
  }
  return false;
}

/*****************************************************************
            Interface to Readline Completion
******************************************************************/

#ifdef HAVE_READLINE

static char *new_command_generator(const char *text, int);
static char **new_mysql_completion(const char *text, int start, int end);

/*
  Tell the GNU Readline library how to complete.  We want to try to complete
  on command names if this is the first word in the line, or on filenames
  if not.
*/

#if defined(EDITLINE_HAVE_COMPLETION_CHAR)
char *no_completion(const char *, int) { return nullptr; }
#elif defined(EDITLINE_HAVE_COMPLETION_INT)
int no_completion(const char *, int) { return 0; }
#else
char *no_completion() { return nullptr; }
#endif

/*
  returns 0 if line matches the previous history entry
  returns 1 if the line doesn't match the previous history entry
*/
static int not_in_history(const char *line) {
  HIST_ENTRY *oldhist = history_get(history_length);

  if (oldhist == nullptr) return 1;
  if (strcmp(oldhist->line, line) == 0) return 0;
  return 1;
}

#if defined(USE_NEW_EDITLINE_INTERFACE)
static int fake_magic_space(int, int)
#else
static int fake_magic_space(const char *, int)
#endif
{
  rl_insert(1, ' ');
  return 0;
}

static void initialize_readline(char *name) {
  /* Allow conditional parsing of the ~/.inputrc file. */
  rl_readline_name = name;

  /* Accept all locales. */
  setlocale(LC_ALL, "");

  /* Tell the completer that we want a crack first. */
#if defined(EDITLINE_HAVE_COMPLETION_CHAR)
  rl_attempted_completion_function = &new_mysql_completion;
  rl_completion_entry_function = &no_completion;

  rl_add_defun("magic-space", &fake_magic_space, -1);
#elif defined(EDITLINE_HAVE_COMPLETION_INT)
  rl_attempted_completion_function = &new_mysql_completion;
  rl_completion_entry_function = &no_completion;
  rl_add_defun("magic-space", &fake_magic_space, -1);
#else
  rl_attempted_completion_function = (CPPFunction *)&new_mysql_completion;
  rl_completion_entry_function = &no_completion;
#endif
}

/*
  Attempt to complete on the contents of TEXT.  START and END show the
  region of TEXT that contains the word to complete.  We can use the
  entire line in case we want to do some simple parsing.  Return the
  array of matches, or NULL if there aren't any.
*/

static char **new_mysql_completion(const char *text, int start [[maybe_unused]],
                                   int end [[maybe_unused]]) {
  if (!status.batch && !quick)
#if defined(USE_NEW_EDITLINE_INTERFACE)
    return rl_completion_matches(text, new_command_generator);
#else
    return completion_matches(const_cast<char *>(text), new_command_generator);
#endif
  else
    return (char **)nullptr;
}

static char *new_command_generator(const char *text, int state) {
  static int textlen;
  char *ptr;
  static Bucket *b;
  static entry *e;
  static uint i;

  if (!state) textlen = (uint)strlen(text);

  if (textlen > 0) { /* lookup in the hash */
    if (!state) {
      uint len;

      b = find_all_matches(&ht, text, (uint)strlen(text), &len);
      if (!b) return NullS;
      e = b->pData;
    }

    if (e) {
      ptr = strdup(e->str);
      e = e->pNext;
      return ptr;
    }
  } else { /* traverse the entire hash, ugly but works */

    if (!state) {
      /* find the first used bucket */
      for (i = 0; i < ht.nTableSize; i++) {
        if (ht.arBuckets[i]) {
          b = ht.arBuckets[i];
          e = b->pData;
          break;
        }
      }
    }
    ptr = NullS;
    while (e && !ptr) { /* find valid entry in bucket */
      if ((uint)strlen(e->str) == b->nKeyLength) ptr = strdup(e->str);
      /* find the next used entry */
      e = e->pNext;
      if (!e) { /* find the next used bucket */
        b = b->pNext;
        if (!b) {
          for (i++; i < ht.nTableSize; i++) {
            if (ht.arBuckets[i]) {
              b = ht.arBuckets[i];
              e = b->pData;
              break;
            }
          }
        } else
          e = b->pData;
      }
    }
    if (ptr) return ptr;
  }
  return NullS;
}

/* Build up the completion hash */

static void build_completion_hash(bool rehash, bool write_info) {
  COMMANDS *cmd = commands;
  MYSQL_RES *databases = nullptr, *tables = nullptr;
  MYSQL_RES *fields;
  static char ***field_names = nullptr;
  MYSQL_ROW database_row, table_row;
  MYSQL_FIELD *sql_field;
  char buf[NAME_LEN * 2 + 2];  // table name plus field name plus 2
  int i, j, num_fields;
  DBUG_TRACE;

#ifndef NDEBUG
  if (!opt_build_completion_hash)
#endif
  {
    if (status.batch || quick || !current_db)
      return;  // We don't need completion in batches
  }

  if (!rehash) return;

  /* Free old used memory */
  if (field_names) field_names = nullptr;
  completion_hash_clean(&ht);
  hash_mem_root.Clear();

  /* hash this file's known subset of SQL commands */
  while (cmd->name) {
    add_word(&ht, cmd->name);
    cmd++;
  }

  /* hash MySQL functions (to be implemented) */

  /* hash all database names */
  if (mysql_query(&mysql_handle, "show databases") == 0) {
    databases = mysql_store_result(&mysql_handle);
    if (!databases)
      put_info(mysql_error(&mysql_handle), INFO_INFO);
    else {
      while ((database_row = mysql_fetch_row(databases))) {
        char *str = strdup_root(&hash_mem_root, (char *)database_row[0]);
        if (str) add_word(&ht, (char *)str);
      }
      mysql_free_result(databases);
    }
  }
  /* hash all table names */
  if (mysql_query(&mysql_handle, "show tables") == 0) {
    tables = mysql_store_result(&mysql_handle);
    if (!tables)
      put_info(mysql_error(&mysql_handle), INFO_INFO);
    else {
      if (mysql_num_rows(tables) > 0 && !opt_silent && write_info) {
        tee_fprintf(stdout,
                    "\
Reading table information for completion of table and column names\n\
You can turn off this feature to get a quicker startup with -A\n\n");
      }
      while ((table_row = mysql_fetch_row(tables))) {
        char *str = strdup_root(&hash_mem_root, (char *)table_row[0]);
        if (str && !completion_hash_exists(&ht, (char *)str, (uint)strlen(str)))
          add_word(&ht, str);
      }
    }
  }

  /* hash all field names, both with the table prefix and without it */
  if (!tables) /* no tables */
  {
    return;
  }
  mysql_data_seek(tables, 0);
  field_names = (char ***)hash_mem_root.Alloc(
      sizeof(char **) * (uint)(mysql_num_rows(tables) + 1));
  if (!field_names) {
    mysql_free_result(tables);
    return;
  }
  i = 0;
  while ((table_row = mysql_fetch_row(tables))) {
    if ((fields = mysql_list_fields(&mysql_handle, (const char *)table_row[0],
                                    NullS))) {
      num_fields = mysql_num_fields(fields);
      field_names[i] =
          (char **)hash_mem_root.Alloc(sizeof(char *) * (num_fields * 2 + 1));
      if (!field_names[i]) {
        mysql_free_result(fields);
        break;
      }
      field_names[i][num_fields * 2] = nullptr;
      j = 0;
      while ((sql_field = mysql_fetch_field(fields))) {
        sprintf(buf, "%.64s.%.64s", table_row[0], sql_field->name);
        field_names[i][j] = strdup_root(&hash_mem_root, buf);
        add_word(&ht, field_names[i][j]);
        field_names[i][num_fields + j] =
            strdup_root(&hash_mem_root, sql_field->name);
        if (!completion_hash_exists(
                &ht, field_names[i][num_fields + j],
                (uint)strlen(field_names[i][num_fields + j])))
          add_word(&ht, field_names[i][num_fields + j]);
        j++;
      }
      mysql_free_result(fields);
    } else
      field_names[i] = nullptr;

    i++;
  }
  mysql_free_result(tables);
  field_names[i] = nullptr;  // End pointer
}

/* for gnu readline */

#ifndef HAVE_INDEX
extern "C" {

char *index(const char *s, int c) {
  for (;;) {
    if (*s == (char)c) return (char *)s;
    if (!*s++) return NullS;
  }
}

char *rindex(const char *s, int c) {
  char *t;

  t = NullS;
  do
    if (*s == (char)c) t = (char *)s;
  while (*s++);
  return (char *)t;
}
}
#endif /* ! HAVE_INDEX */
#endif /* HAVE_READLINE */

static void fix_line(String *final_command) {
  int total_lines = 1;
  char *ptr = final_command->c_ptr();
  String fixed_buffer; /* Converted buffer */

  /* Character if we are in a string or not */
  char str_char = '\0';

  /* find out how many lines we have and remove newlines */
  while (*ptr != '\0') {
    switch (*ptr) {
      /* string character */
      case '"':
      case '\'':
      case '`':
        if (str_char == '\0') /* open string */
          str_char = *ptr;
        else if (str_char == *ptr) /* close string */
          str_char = '\0';
        fixed_buffer.append(ptr, 1);
        break;
      case '\n':
        /* not in string, change to space if in string, leave it alone */
        fixed_buffer.append(str_char == '\0' ? " " : "\n");
        total_lines++;
        break;
      case '\\':
        fixed_buffer.append('\\');
        /* need to see if the backslash is escaping anything */
        if (str_char) {
          ptr++;
          /* special characters that need escaping */
          if (*ptr == '\'' || *ptr == '"' || *ptr == '\\')
            fixed_buffer.append(ptr, 1);
          else
            ptr--;
        }
        break;

      default:
        fixed_buffer.append(ptr, 1);
    }
    ptr++;
  }
  if (total_lines > 1) add_filtered_history(fixed_buffer.ptr());
}

/* Add the given line to mysql history and syslog. */
static void add_filtered_history(const char *string) {
  // line shouldn't be on history ignore list
  if (ignore_matcher.is_matching(string, charset_info)) return;

#ifdef HAVE_READLINE
  if (!quick && not_in_history(string)) add_history(string);
#endif

  if (opt_syslog) add_syslog(string);
}

void add_syslog(const char *line) {
  char buff[MAX_SYSLOG_MESSAGE_SIZE];
  snprintf(buff, sizeof(buff),
           "SYSTEM_USER:'%s', MYSQL_USER:'%s', "
           "CONNECTION_ID:%lu, DB_SERVER:'%s', DB:'%s', QUERY:'%s'",
           /* use the cached user/sudo_user value. */
           current_os_sudouser ? current_os_sudouser
           : current_os_user   ? current_os_user
                               : "--",
           current_user ? current_user : "--", mysql_thread_id(&mysql_handle),
           current_host ? current_host : "--", current_db ? current_db : "--",
           line);

  (void)my_syslog(charset_info, INFORMATION_LEVEL, buff);
}

static int reconnect() {
  /* purecov: begin tested */
  if (opt_reconnect) {
    put_info("No connection. Trying to reconnect...", INFO_INFO);
    (void)com_connect((String *)nullptr, nullptr);
    if (opt_rehash && connected) com_rehash(nullptr, nullptr);
  }
  if (!connected) return put_info("Can't connect to the server\n", INFO_ERROR);
  /* purecov: end */
  return 0;
}

/**
  Checks the current DB and updates the global variable current_db
  If the command fails hen he current_db is set to nullptr.

  @return Error state
    @retval true An error occurred
    @retval false Success; current_db is updated
*/
static bool get_current_db() {
  MYSQL_RES *res;

  /* If one_database is set, current_db is not supposed to change. */
  if (one_database) return false;

  my_free(current_db);
  current_db = nullptr;
  /* In case of error below current_db will be NULL */
  if (!mysql_query(&mysql_handle, "SELECT DATABASE()") &&
      (res = mysql_use_result(&mysql_handle))) {
    MYSQL_ROW row = mysql_fetch_row(res);
    if (row && row[0])
      current_db = my_strdup(PSI_NOT_INSTRUMENTED, row[0], MYF(MY_WME));
    mysql_free_result(res);
  } else {
    /* We failed to issue the command and we likely lost connection */
    return true;
  }
  return false;
}

/***************************************************************************
 The different commands
***************************************************************************/

static int mysql_real_query_for_lazy(const char *buf, size_t length,
                                     bool set_params = false) {
  int error = 0;
  for (uint retry = 0;; retry++) {
    error = 0;

    if (telemetry_client_attrs != nullptr) {
      telemetry_client_attrs->set_params(&mysql_handle);
    }

    if (set_params && global_attrs->set_params(&mysql_handle)) break;
    if (!mysql_real_query(&mysql_handle, buf, (ulong)length)) break;
    error = put_error(&mysql_handle);
    if ((mysql_errno(&mysql_handle) != CR_SERVER_GONE_ERROR &&
         mysql_errno(&mysql_handle) != CR_SERVER_LOST &&
         mysql_handle.net.error != NET_ERROR_SOCKET_UNUSABLE) ||
        retry > 1 || !opt_reconnect)
      break;
    if (reconnect()) break;
  }

  if (telemetry_client_attrs != nullptr) {
    telemetry_client_attrs->clear(connected ? &mysql_handle : nullptr);
  }

  if (set_params) global_attrs->clear(connected ? &mysql_handle : nullptr);
  return error;
}

static int mysql_store_result_for_lazy(MYSQL_RES **result) {
  *result = mysql_store_result(&mysql_handle);
  if (*result) return 0;

  if (mysql_error(&mysql_handle)[0]) return put_error(&mysql_handle);
  return 0;
}

static void print_help_item(MYSQL_ROW *cur, int num_name, int num_cat,
                            char *last_char) {
  const char ccat = (*cur)[num_cat][0];
  if (*last_char != ccat) {
    put_info(ccat == 'Y' ? "categories:" : "topics:", INFO_INFO);
    *last_char = ccat;
  }
  tee_fprintf(PAGER, "   %s\n", (*cur)[num_name]);
}

static int com_server_help(String *buffer [[maybe_unused]],
                           char *line [[maybe_unused]], char *help_arg) {
  MYSQL_ROW cur;
  const char *server_cmd;
  char cmd_buf[100 + 1];
  MYSQL_RES *result;
  int error;

  if (help_arg[0] != '\'') {
    char *end_arg = strend(help_arg);
    if (--end_arg) {
      while (my_isspace(charset_info, *end_arg)) end_arg--;
      *++end_arg = '\0';
    }
    (void)strxnmov(cmd_buf, sizeof(cmd_buf), "help '", help_arg, "'", NullS);
  } else
    (void)strxnmov(cmd_buf, sizeof(cmd_buf), "help ", help_arg, NullS);

  server_cmd = cmd_buf;

  if (!status.batch) {
    old_buffer.copy(*buffer);
  }

  if (!connected && reconnect()) return 1;

  if ((error =
           mysql_real_query_for_lazy(server_cmd, (int)strlen(server_cmd))) ||
      (error = mysql_store_result_for_lazy(&result)))
    return error;

  if (result) {
    const unsigned int num_fields = mysql_num_fields(result);
    const uint64_t num_rows = mysql_num_rows(result);
    mysql_fetch_fields(result);
    if (num_fields == 3 && num_rows == 1) {
      cur = mysql_fetch_row(result);
      if (!cur) {
        error = -1;
        goto err;
      }

      init_pager();
      tee_fprintf(PAGER, "Name: \'%s\'\n", cur[0]);
      tee_fprintf(PAGER, "Description:\n%s", cur[1]);
      if (cur[2] && *((char *)cur[2]))
        tee_fprintf(PAGER, "Examples:\n%s", cur[2]);
      tee_fprintf(PAGER, "\n");
      end_pager();
    } else if (num_fields >= 2 && num_rows) {
      init_pager();
      char last_char = 0;

      int num_name = 0, num_cat = 0;

      if (num_fields == 2) {
        put_info("Many help items for your request exist.", INFO_INFO);
        put_info(
            "To make a more specific request, please type 'help "
            "<item>',\nwhere <item> is one of the following",
            INFO_INFO);
        num_name = 0;
        num_cat = 1;
      } else if ((cur = mysql_fetch_row(result))) {
        tee_fprintf(PAGER, "You asked for help about help category: \"%s\"\n",
                    cur[0]);
        put_info(
            "For more information, type 'help <item>', where <item> is one "
            "of "
            "the following",
            INFO_INFO);
        num_name = 1;
        num_cat = 2;
        print_help_item(&cur, 1, 2, &last_char);
      }

      while ((cur = mysql_fetch_row(result)))
        print_help_item(&cur, num_name, num_cat, &last_char);
      tee_fprintf(PAGER, "\n");
      end_pager();
    } else {
      put_info("\nNothing found", INFO_INFO);
      if (native_strncasecmp(server_cmd, "help 'contents'", 15) == 0) {
        put_info("\nPlease check if 'help tables' are loaded.\n", INFO_INFO);
        goto err;
      }
      put_info(
          "Please try to run 'help contents' for a list of all accessible "
          "topics\n",
          INFO_INFO);
    }
  }

err:
  mysql_free_result(result);
  return error;
}

static int com_help(String *buffer [[maybe_unused]],
                    char *line [[maybe_unused]]) {
  int i, j;
  char *help_arg = strchr(line, ' '), buff[32], *end;
  if (help_arg) {
    while (my_isspace(charset_info, *help_arg)) help_arg++;
    if (*help_arg) return com_server_help(buffer, line, help_arg);
  }

  put_info(
      "\nFor information about MySQL products and services, visit:\n"
      "   https://www.mysql.com/\n"
      "For developer information, including the MySQL Reference Manual, "
      "visit:\n"
      "   https://dev.mysql.com/\n"
      "To buy MySQL Enterprise support, training, or other products, visit:\n"
      "   https://shop.mysql.com/\n",
      INFO_INFO);
  put_info("List of all MySQL commands:", INFO_INFO);
  if (!named_cmds)
    put_info(
        "Note that all text commands must be first on line and end with ';'",
        INFO_INFO);
  for (i = 0; commands[i].name; i++) {
    end = my_stpcpy(buff, commands[i].name);
    for (j = (int)strlen(commands[i].name); j < 10; j++)
      end = my_stpcpy(end, " ");
    if (commands[i].func) {
      if (commands[i].cmd_char)
        tee_fprintf(stdout, "%s(\\%c) %s\n", buff, commands[i].cmd_char,
                    commands[i].doc);
      else
        tee_fprintf(stdout, "%s %s\n", buff, commands[i].doc);
    }
  }
  if (connected && mysql_get_server_version(&mysql_handle) >= 40100)
    put_info("\nFor server side help, type 'help contents'\n", INFO_INFO);
  return 0;
}

/* ARGSUSED */
static int com_clear(String *buffer, char *line [[maybe_unused]]) {
  if (status.add_to_history) fix_line(buffer);
  buffer->length(0);
  return 0;
}

/* ARGSUSED */
static int com_charset(String *buffer [[maybe_unused]], char *line) {
  char buff[256], *param;
  const CHARSET_INFO *new_cs;
  strmake(buff, line, sizeof(buff) - 1);
  param = get_arg(buff, false);
  if (!param || !*param) {
    return put_info("Usage: \\C charset_name | charset charset_name",
                    INFO_ERROR, 0);
  }
  new_cs = get_charset_by_csname(param, MY_CS_PRIMARY, MYF(MY_WME));
  if (new_cs) {
    charset_info = new_cs;
    mysql_set_character_set(&mysql_handle, charset_info->csname);
    default_charset = charset_info->csname;
    put_info("Charset changed", INFO_INFO);
  } else
    put_info("Charset is not found", INFO_INFO);
  return 0;
}

/*
  Execute command
  Returns: 0  if ok
          -1 if not fatal error
          1  if fatal error
*/

static int com_go_impl(String *buffer, char *line [[maybe_unused]]) {
  char buff[200];             /* about 110 chars used so far */
  char time_buff[52 + 3 + 1]; /* time max + space&parens + NUL */
  MYSQL_RES *result;
  ulong warnings = 0;
  ulonglong timer;
  uint error = 0;
  int err = 0;

  interrupted_query = false;
  if (!status.batch) {
    old_buffer.copy(*buffer);  // Save for edit command
  }

  /* Remove garbage for nicer messages */
  buff[0] = 0;
  remove_cntrl(buffer);

  if (buffer->is_empty()) {
    if (status.batch)  // Ignore empty quries
      return 0;
    return put_info("No query specified\n", INFO_ERROR);
  }
  if (!connected && reconnect()) {
    buffer->length(0);              // Remove query on error
    return opt_reconnect ? -1 : 1;  // Fatal error
  }
  if (verbose) (void)com_print(buffer, nullptr);

  if (skip_updates && (buffer->length() < 4 ||
                       my_strnncoll(charset_info, (const uchar *)buffer->ptr(),
                                    4, (const uchar *)"SET ", 4))) {
    (void)put_info("Ignoring query to other database", INFO_INFO);
    return 0;
  }

  timer = my_timer_microseconds();
  executing_query = true;
  error = mysql_real_query_for_lazy(buffer->ptr(), buffer->length(), true);

  if (status.add_to_history) {
    buffer->append(vertical ? "\\G" : delimiter);
    /* Append final command onto history and syslog. */
    fix_line(buffer);
  }
  buffer->length(0);

  if (error) goto end;

  do {
    char *pos;
    const bool batchmode = (status.batch && verbose <= 1);
    buff[0] = 0;

    if (quick) {
      result = mysql_use_result(&mysql_handle);
      if (!result && mysql_field_count(&mysql_handle)) {
        error = put_error(&mysql_handle);
        goto end;
      }
    } else {
      error = mysql_store_result_for_lazy(&result);
      if (error) goto end;
    }

    if (verbose >= 3 || !opt_silent)
      mysql_end_timer(timer, time_buff);
    else
      time_buff[0] = '\0';

    /* Every branch must truncate  buff . */
    if (result) {
      if (!mysql_num_rows(result) && !quick && !column_types_flag) {
        my_stpcpy(buff, "Empty set");
        if (opt_xml) {
          /*
            We must print XML header and footer
            to produce a well-formed XML even if
            the result set is empty (Bug#27608).
          */
          init_pager();
          print_table_data_xml(result);
          end_pager();
        }
      } else {
        init_pager();
        if (opt_html)
          print_table_data_html(result);
        else if (opt_xml)
          print_table_data_xml(result);
        else if (vertical || (auto_vertical_output &&
                              (terminal_width < get_result_width(result))))
          print_table_data_vertically(result);
        else if (opt_silent && verbose <= 2 && !output_tables)
          print_tab_data(result);
        else
          print_table_data(result);
        if (!batchmode)
          sprintf(buff, "%" PRId64 " %s in set", mysql_num_rows(result),
                  mysql_num_rows(result) == 1LL ? "row" : "rows");
        end_pager();
        if (mysql_errno(&mysql_handle)) error = put_error(&mysql_handle);
      }
    } else if (mysql_affected_rows(&mysql_handle) == ~(ulonglong)0)
      my_stpcpy(buff, "Query OK");
    else if (!batchmode)
      sprintf(buff, "Query OK, %" PRId64 " %s affected",
              mysql_affected_rows(&mysql_handle),
              mysql_affected_rows(&mysql_handle) == 1LL ? "row" : "rows");

    pos = strend(buff);
    warnings = mysql_warning_count(&mysql_handle);
    if (warnings && !batchmode) {
      *pos++ = ',';
      *pos++ = ' ';
      pos = longlong10_to_str(warnings, pos, 10);
      pos = my_stpcpy(pos, " warning");
      if (warnings != 1) *pos++ = 's';
    }
    my_stpcpy(pos, time_buff);
    put_info(buff, INFO_RESULT);
    if (mysql_info(&mysql_handle))
      put_info(mysql_info(&mysql_handle), INFO_RESULT);
    put_info("", INFO_RESULT);  // Empty row

    if (result && !mysql_eof(result)) /* Something wrong when using quick */
      error = put_error(&mysql_handle);
    else if (unbuffered)
      fflush(stdout);
    mysql_free_result(result);
  } while (!(err = mysql_next_result(&mysql_handle)));
  if (err >= 1) error = put_error(&mysql_handle);

end:

  /* Show warnings if any or error occurred */
  if (show_warnings == 1 && (warnings >= 1 || error)) print_warnings();

  if (!error && (mysql_handle.server_status & SERVER_STATUS_DB_DROPPED))
    get_current_db();

  executing_query = false;
  return error; /* New command follows */
}

static void telemetry_carrier_set(void *carrier_data, const char *key,
                                  size_t key_length, const char *value,
                                  size_t value_length) {
  auto *qa = reinterpret_cast<client_query_attributes *>(carrier_data);
  assert(qa != nullptr);
  qa->push_param(key, key_length, value, value_length);
}

static int com_go(String *buffer, char *line) {
  int rc;
  telemetry_span_t *span = nullptr;

  if (client_telemetry_plugin) {
    span = client_telemetry_plugin->start_span("client");

    if (span != nullptr) {
      client_telemetry_plugin->injector(span, telemetry_client_attrs,
                                        telemetry_carrier_set);
    }
  }

  rc = com_go_impl(buffer, line);

  if (client_telemetry_plugin) {
    client_telemetry_plugin->end_span(span);
  }

  return rc;
}

static void init_pager() {
#ifdef USE_POPEN
  if (!opt_nopager) {
    PAGER = popen(pager, "w");
    if (!PAGER) {
      tee_fprintf(stdout, "popen() failed! defaulting PAGER to stdout!\n");
      PAGER = stdout;
    }
  } else
#endif
    PAGER = stdout;
}

static void end_pager() {
#ifdef USE_POPEN
  if (!opt_nopager) pclose(PAGER);
#endif
}

static void init_tee(const char *file_name) {
  FILE *new_outfile;
  if (opt_outfile) end_tee();
  new_outfile = my_fopen(file_name, O_APPEND | O_WRONLY, MYF(MY_WME));
  if (!new_outfile) {
    tee_fprintf(stdout, "Error logging to file '%s'\n", file_name);
    return;
  }
  OUTFILE = new_outfile;
  strmake(outfile, file_name, FN_REFLEN - 1);
  tee_fprintf(stdout, "Logging to file '%s'\n", file_name);
  opt_outfile = true;
}

static void end_tee() {
  my_fclose(OUTFILE, MYF(0));
  OUTFILE = nullptr;
  opt_outfile = false;
}

static int com_ego(String *buffer, char *line) {
  int result;
  const bool oldvertical = vertical;
  vertical = true;
  result = com_go(buffer, line);
  vertical = oldvertical;
  return result;
}

const char *fieldtype2str(enum enum_field_types type);

static char *fieldflags2str(uint f) {
  static char buf[1024];
  char *s = buf;
  *s = 0;
#define ff2s_check_flag(X)    \
  if (f & X##_FLAG) {         \
    s = my_stpcpy(s, #X " "); \
    f &= ~X##_FLAG;           \
  }
  ff2s_check_flag(NOT_NULL);
  ff2s_check_flag(PRI_KEY);
  ff2s_check_flag(UNIQUE_KEY);
  ff2s_check_flag(MULTIPLE_KEY);
  ff2s_check_flag(BLOB);
  ff2s_check_flag(UNSIGNED);
  ff2s_check_flag(ZEROFILL);
  ff2s_check_flag(BINARY);
  ff2s_check_flag(ENUM);
  ff2s_check_flag(AUTO_INCREMENT);
  ff2s_check_flag(TIMESTAMP);
  ff2s_check_flag(SET);
  ff2s_check_flag(NO_DEFAULT_VALUE);
  ff2s_check_flag(NUM);
  ff2s_check_flag(PART_KEY);
  ff2s_check_flag(UNIQUE);
  ff2s_check_flag(BINCMP);
  ff2s_check_flag(ON_UPDATE_NOW);
#undef ff2s_check_flag
  if (f) sprintf(s, " unknows=0x%04x", f);
  return buf;
}

static void print_field_types(MYSQL_RES *result) {
  MYSQL_FIELD *field;
  uint i = 0;

  while ((field = mysql_fetch_field(result))) {
    tee_fprintf(PAGER,
                "Field %3u:  `%s`\n"
                "Catalog:    `%s`\n"
                "Database:   `%s`\n"
                "Table:      `%s`\n"
                "Org_table:  `%s`\n"
                "Type:       %s\n"
                "Collation:  %s (%u)\n"
                "Length:     %lu\n"
                "Max_length: %lu\n"
                "Decimals:   %u\n"
                "Flags:      %s\n\n",
                ++i, field->name, field->catalog, field->db, field->table,
                field->org_table, fieldtype2str(field->type),
                get_collation_name(field->charsetnr), field->charsetnr,
                field->length, field->max_length, field->decimals,
                fieldflags2str(field->flags));
  }
  tee_puts("", PAGER);
}

/* Used to determine if we should invoke print_as_hex for this field */

static bool is_binary_field(MYSQL_FIELD *field) {
  return (
      (field->charsetnr == 63) &&
      (field->type == MYSQL_TYPE_BIT || field->type == MYSQL_TYPE_BLOB ||
       field->type == MYSQL_TYPE_LONG_BLOB ||
       field->type == MYSQL_TYPE_MEDIUM_BLOB ||
       field->type == MYSQL_TYPE_TINY_BLOB ||
       field->type == MYSQL_TYPE_VAR_STRING ||
       field->type == MYSQL_TYPE_STRING || field->type == MYSQL_TYPE_VARCHAR ||
       field->type == MYSQL_TYPE_VECTOR || field->type == MYSQL_TYPE_GEOMETRY));
}

/* Print binary value as hex literal (0x ...) */

static void print_as_hex(FILE *output_file, const char *str, ulong len,
                         ulong total_bytes_to_send) {
  const auto *ptr = pointer_cast<const unsigned char *>(str);
  const unsigned char *end = ptr + len;
  ulong i;

  if (str != nullptr) {
    fprintf(output_file, "0x");
    ulong remaining = len;
    static const unsigned char hex_digits[] = "0123456789ABCDEF";
    unsigned char chunk_buf[64];
    while (ptr < end) {
      // write up to 32 input bytes at a time for performance
      // (up to 64 bytes of hex output)
      const size_t chunk_input_size = std::min((uint)remaining, 32U);
      for (size_t j = 0; j < chunk_input_size; j++) {
        const size_t offset = 2 * j;
        chunk_buf[offset] = hex_digits[(*ptr >> 4) & 0x0F];
        chunk_buf[offset + 1] = hex_digits[(*ptr) & 0x0F];
        ptr++;
      }
      fwrite(chunk_buf, 1, 2 * chunk_input_size, output_file);
      remaining -= chunk_input_size;
    }
    /* Printed string length: two chars "0x" + two chars for each byte. */
    i = 2 + len * 2;
  } else {
    i = fprintf(output_file, "NULL");
  }
  for (; i < total_bytes_to_send; i++)
    tee_putc(static_cast<int>(' '), output_file);
}

static void print_table_data(MYSQL_RES *result) {
  String separator(256);
  MYSQL_ROW cur;
  MYSQL_FIELD *field;
  bool *num_flag;
  size_t sz;

  if (column_types_flag) {
    print_field_types(result);
    if (!mysql_num_rows(result)) return;
    mysql_field_seek(result, 0);
  }
  sz = sizeof(bool) * mysql_num_fields(result);
  num_flag = (bool *)my_safe_alloca(sz, MAX_ALLOCA_SIZE);
  separator.copy("+", 1, charset_info);
  while ((field = mysql_fetch_field(result))) {
    size_t length = column_names ? field->name_length : 0;
    if (quick)
      length = max<size_t>(length, field->length);
    else
      length = max<size_t>(length, field->max_length);
    if (length < 4 && !IS_NOT_NULL(field->flags))
      length = 4;  // Room for "NULL"
    if (opt_binhex && is_binary_field(field)) length = 2 + length * 2;
    field->max_length = (ulong)length;
    separator.fill(separator.length() + length + 2, '-');
    separator.append('+');
  }
  separator.append('\0');  // End marker for \0
  tee_puts(separator.ptr(), PAGER);
  if (column_names) {
    mysql_field_seek(result, 0);
    (void)tee_fputs("|", PAGER);
    for (uint off = 0; (field = mysql_fetch_field(result)); off++) {
      const size_t name_length = strlen(field->name);
      const size_t numcells = charset_info->cset->numcells(
          charset_info, field->name, field->name + name_length);
      const size_t display_length = field->max_length + name_length - numcells;
      tee_fprintf(PAGER, " %-*s |",
                  min<int>((int)display_length, MAX_COLUMN_LENGTH),
                  field->name);
      num_flag[off] = IS_NUM(field->type);
    }
    (void)tee_fputs("\n", PAGER);
    tee_puts(separator.ptr(), PAGER);
  }

  const uint num_fields = mysql_num_fields(result);

  while ((cur = mysql_fetch_row(result))) {
    ulong *lengths = mysql_fetch_lengths(result);
    (void)tee_fputs("| ", PAGER);
    mysql_field_seek(result, 0);
    for (uint off = 0; off < num_fields; off++) {
      const char *buffer;
      uint data_length;
      uint field_max_length;
      size_t visible_length;
      uint extra_padding;

      if (off) (void)tee_fputs(" ", PAGER);

      if (cur[off] == nullptr) {
        buffer = "NULL";
        data_length = 4;
      } else {
        buffer = cur[off];
        data_length = (uint)lengths[off];
      }

      field = mysql_fetch_field(result);
      field_max_length = field->max_length;

      if (opt_binhex && is_binary_field(field))
        print_as_hex(PAGER, cur[off], lengths[off], field_max_length);
      else {
        /*
          How many text cells on the screen will this string span?  If it
          contains multibyte characters, then the number of characters we occupy
          on screen will be fewer than the number of bytes we occupy in memory.

          We need to find how much screen real-estate we will occupy to know how
          many extra padding-characters we should send with the printing
          function.
        */
        visible_length = charset_info->cset->numcells(charset_info, buffer,
                                                      buffer + data_length);
        extra_padding = (uint)(data_length - visible_length);

        if (field_max_length > MAX_COLUMN_LENGTH)
          tee_print_sized_data(buffer, data_length,
                               MAX_COLUMN_LENGTH + extra_padding, false);
        else {
          if (num_flag[off] != 0) /* if it is numeric, we right-justify it */
            tee_print_sized_data(buffer, data_length,
                                 field_max_length + extra_padding, true);
          else
            tee_print_sized_data(buffer, data_length,
                                 field_max_length + extra_padding, false);
        }
      }
      tee_fputs(" |", PAGER);
    }
    (void)tee_fputs("\n", PAGER);

    // Check interrupted_query last; this ensures that we get at least one
    // row. This is useful for aborted EXPLAIN ANALYZE queries.
    if (interrupted_query) break;
  }
  tee_puts(separator.ptr(), PAGER);
  my_safe_afree((bool *)num_flag, sz, MAX_ALLOCA_SIZE);
}

/**
  Return the length of a field after it would be rendered into text.

  This doesn't know or care about multibyte characters.  Assume we're
  using such a charset.  We can't know that all of the upcoming rows
  for this column will have bytes that each render into some fraction
  of a character.  It's at least possible that a row has bytes that
  all render into one character each, and so the maximum length is
  still the number of bytes.  (Assumption 1:  This can't be better
  because we can never know the number of characters that the DB is
  going to send -- only the number of bytes.  2: Chars <= Bytes.)

  @param  field  Pointer to a field to be inspected

  @returns  number of character positions to be used, at most
*/
static int get_field_disp_length(MYSQL_FIELD *field) {
  uint length = column_names ? field->name_length : 0;

  if (quick)
    length = max<uint>(length, field->length);
  else
    length = max<uint>(length, field->max_length);

  if (length < 4 && !IS_NOT_NULL(field->flags))
    length = 4; /* Room for "NULL" */

  return length;
}

/**
  For a new result, return the max number of characters that any
  upcoming row may return.

  @param  result  Pointer to the result to judge

  @returns  The max number of characters in any row of this result
*/
static int get_result_width(MYSQL_RES *result) {
  unsigned int len = 0;
  MYSQL_FIELD *field;
  MYSQL_FIELD_OFFSET offset;

#ifndef NDEBUG
  offset = mysql_field_tell(result);
  assert(offset == 0);
#else
  offset = 0;
#endif

  while ((field = mysql_fetch_field(result)) != nullptr)
    len +=
        get_field_disp_length(field) + 3; /* plus bar, space, & final space */

  (void)mysql_field_seek(result, offset);

  return len + 1; /* plus final bar. */
}

static void tee_print_sized_data(const char *data, unsigned int data_length,
                                 unsigned int total_bytes_to_send,
                                 bool right_justified) {
  /*
    For '\0's print ASCII spaces instead, as '\0' is eaten by (at
    least my) console driver, and that messes up the pretty table
    grid.  (The \0 is also the reason we can't use fprintf() .)
  */
  unsigned int i;

  if (right_justified)
    for (i = data_length; i < total_bytes_to_send; i++)
      tee_putc((int)' ', PAGER);

  tee_write(PAGER, data, data_length, MY_PRINT_SPS_0 | MY_PRINT_MB);

  if (!right_justified)
    for (i = data_length; i < total_bytes_to_send; i++)
      tee_putc((int)' ', PAGER);
}

static void print_table_data_html(MYSQL_RES *result) {
  MYSQL_ROW cur;
  MYSQL_FIELD *field;

  mysql_field_seek(result, 0);
  (void)tee_fputs("<TABLE BORDER=1><TR>", PAGER);
  if (column_names) {
    while ((field = mysql_fetch_field(result))) {
      tee_fputs("<TH>", PAGER);
      if (field->name && field->name[0])
        xmlencode_print(field->name, field->name_length);
      else
        tee_fputs(field->name ? " &nbsp; " : "NULL", PAGER);
      tee_fputs("</TH>", PAGER);
    }
    (void)tee_fputs("</TR>", PAGER);
  }
  while ((cur = mysql_fetch_row(result))) {
    if (interrupted_query) break;
    ulong *lengths = mysql_fetch_lengths(result);
    field = mysql_fetch_fields(result);
    (void)tee_fputs("<TR>", PAGER);
    for (uint i = 0; i < mysql_num_fields(result); i++) {
      (void)tee_fputs("<TD>", PAGER);
      if (opt_binhex && is_binary_field(&field[i]))
        print_as_hex(PAGER, cur[i], lengths[i], lengths[i]);
      else
        xmlencode_print(cur[i], lengths[i]);
      (void)tee_fputs("</TD>", PAGER);
    }
    (void)tee_fputs("</TR>", PAGER);
  }
  (void)tee_fputs("</TABLE>", PAGER);
}

static void print_table_data_xml(MYSQL_RES *result) {
  MYSQL_ROW cur;
  MYSQL_FIELD *fields;

  mysql_field_seek(result, 0);

  tee_fputs("<?xml version=\"1.0\"?>\n\n<resultset statement=\"", PAGER);
  xmlencode_print(glob_buffer.ptr(), (int)strlen(glob_buffer.ptr()));
  tee_fputs("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">",
            PAGER);

  fields = mysql_fetch_fields(result);
  while ((cur = mysql_fetch_row(result))) {
    if (interrupted_query) break;
    ulong *lengths = mysql_fetch_lengths(result);
    (void)tee_fputs("\n  <row>\n", PAGER);
    for (uint i = 0; i < mysql_num_fields(result); i++) {
      tee_fprintf(PAGER, "\t<field name=\"");
      xmlencode_print(fields[i].name, (uint)strlen(fields[i].name));
      if (cur[i]) {
        tee_fprintf(PAGER, "\">");
        if (opt_binhex && is_binary_field(&fields[i]))
          print_as_hex(PAGER, cur[i], lengths[i], lengths[i]);
        else
          xmlencode_print(cur[i], lengths[i]);
        tee_fprintf(PAGER, "</field>\n");
      } else
        tee_fprintf(PAGER, "\" xsi:nil=\"true\" />\n");
    }
    (void)tee_fputs("  </row>\n", PAGER);
  }
  (void)tee_fputs("</resultset>\n", PAGER);
}

static void print_table_data_vertically(MYSQL_RES *result) {
  MYSQL_ROW cur;
  uint max_length = 0;
  MYSQL_FIELD *field;

  while ((field = mysql_fetch_field(result))) {
    const uint length = field->name_length;
    if (length > max_length) max_length = length;
    field->max_length = length;
  }

  mysql_field_seek(result, 0);
  for (uint row_count = 1; (cur = mysql_fetch_row(result)); row_count++) {
    if (interrupted_query) break;
    mysql_field_seek(result, 0);
    tee_fprintf(
        PAGER,
        "*************************** %d. row ***************************\n",
        row_count);

    ulong *lengths = mysql_fetch_lengths(result);

    for (uint off = 0; off < mysql_num_fields(result); off++) {
      field = mysql_fetch_field(result);
      if (column_names)
        tee_fprintf(PAGER, "%*s: ", (int)max_length, field->name);
      if (cur[off]) {
        if (opt_binhex && is_binary_field(field))
          print_as_hex(PAGER, cur[off], lengths[off], lengths[off]);
        else
          tee_write(PAGER, cur[off], lengths[off],
                    MY_PRINT_SPS_0 | MY_PRINT_MB);
        tee_putc('\n', PAGER);
      } else
        tee_fprintf(PAGER, "NULL\n");
    }
  }
}

/* print_warnings should be called right after executing a statement */

static void print_warnings() {
  const char *query;
  MYSQL_RES *result;
  MYSQL_ROW cur;
  uint64_t num_rows;

  /* Save current error before calling "show warnings" */
  const uint error = mysql_errno(&mysql_handle);

  /* Get the warnings */
  query = "show warnings";
  mysql_real_query_for_lazy(query, strlen(query));
  mysql_store_result_for_lazy(&result);

  /* Bail out when no warnings */
  if (!result || !(num_rows = mysql_num_rows(result))) goto end;

  cur = mysql_fetch_row(result);

  /*
    Don't print a duplicate of the current error.  It is possible for SHOW
    WARNINGS to return multiple errors with the same code, but different
    messages.  To be safe, skip printing the duplicate only if it is the only
    warning.
  */
  if (!cur || (num_rows == 1 && error == (uint)strtoul(cur[1], nullptr, 10)))
    goto end;

  /* Print the warnings */
  init_pager();
  do {
    tee_fprintf(PAGER, "%s (Code %s): %s\n", cur[0], cur[1], cur[2]);
  } while ((cur = mysql_fetch_row(result)));
  end_pager();

end:
  mysql_free_result(result);
}

static const char *array_value(const char **array, char key) {
  for (; *array; array += 2)
    if (**array == key) return array[1];
  return nullptr;
}

static void xmlencode_print(const char *src, uint length) {
  if (!src)
    tee_fputs("NULL", PAGER);
  else
    tee_write(PAGER, src, length, MY_PRINT_XML | MY_PRINT_MB);
}

static void safe_put_field(const char *pos, ulong length) {
  if (!pos)
    tee_fputs("NULL", PAGER);
  else {
    const int flags =
        MY_PRINT_MB | (opt_raw_data ? 0 : (MY_PRINT_ESC_0 | MY_PRINT_CTRL));
    /* Can't use tee_fputs(), it stops with NUL characters. */
    tee_write(PAGER, pos, length, flags);
  }
}

static void print_tab_data(MYSQL_RES *result) {
  MYSQL_ROW cur;
  MYSQL_FIELD *field;
  ulong *lengths;

  if (opt_silent < 2 && column_names) {
    int first = 0;
    while ((field = mysql_fetch_field(result))) {
      if (first++) (void)tee_fputs("\t", PAGER);
      (void)tee_fputs(field->name, PAGER);
    }
    (void)tee_fputs("\n", PAGER);
  }
  while ((cur = mysql_fetch_row(result))) {
    lengths = mysql_fetch_lengths(result);
    field = mysql_fetch_fields(result);
    if (opt_binhex && is_binary_field(&field[0]))
      print_as_hex(PAGER, cur[0], lengths[0], lengths[0]);
    else
      safe_put_field(cur[0], lengths[0]);
    for (uint off = 1; off < mysql_num_fields(result); off++) {
      (void)tee_fputs("\t", PAGER);
      if (opt_binhex && field && is_binary_field(&field[off]))
        print_as_hex(PAGER, cur[off], lengths[off], lengths[off]);
      else
        safe_put_field(cur[off], lengths[off]);
    }
    (void)tee_fputs("\n", PAGER);
  }
}

static int com_tee(String *buffer [[maybe_unused]],
                   char *line [[maybe_unused]]) {
  char file_name[FN_REFLEN], *end, *param;

  while (my_isspace(charset_info, *line)) line++;

  param = strchr(line, ' ');
  if (!param)  // if outfile wasn't given, use the default
  {
    if (!strlen(outfile)) {
      printf("No previous outfile available, you must give a filename!\n");
      return 0;
    }
    if (opt_outfile) {
      tee_fprintf(stdout, "Currently logging to file '%s'\n", outfile);
      return 0;
    }
    param = outfile;  // resume using the old outfile
  }

  /* eliminate the spaces before the parameters */
  while (my_isspace(charset_info, *param)) param++;
  end = strmake(file_name, param, sizeof(file_name) - 1);
  /* remove end space from command line */
  while (end > file_name && (my_isspace(charset_info, end[-1]) ||
                             my_iscntrl(charset_info, end[-1])))
    end--;
  end[0] = 0;
  if (end == file_name) {
    printf("No outfile specified!\n");
    return 0;
  }
  init_tee(file_name);
  return 0;
}

static int com_notee(String *buffer [[maybe_unused]],
                     char *line [[maybe_unused]]) {
  if (opt_outfile) end_tee();
  tee_fprintf(stdout, "Outfile disabled.\n");
  return 0;
}

/*
  Sorry, this command is not available in Windows.
*/

#ifdef USE_POPEN
static int com_pager(String *buffer [[maybe_unused]],
                     char *line [[maybe_unused]]) {
  char pager_name[FN_REFLEN], *end, *param;

  if (status.batch) return 0;
  /* Skip spaces in front of the pager command */
  while (my_isspace(charset_info, *line)) line++;
  /* Skip the pager command */
  param = strchr(line, ' ');
  /* Skip the spaces between the command and the argument */
  while (param && my_isspace(charset_info, *param)) param++;
  if (!param || !strlen(param))  // if pager was not given, use the default
  {
    if (!default_pager_set) {
      tee_fprintf(stdout, "Default pager wasn't set, using stdout.\n");
      opt_nopager = true;
      my_stpcpy(pager, "stdout");
      PAGER = stdout;
      return 0;
    }
    my_stpcpy(pager, default_pager);
  } else {
    end = strmake(pager_name, param, sizeof(pager_name) - 1);
    while (end > pager_name && (my_isspace(charset_info, end[-1]) ||
                                my_iscntrl(charset_info, end[-1])))
      end--;
    end[0] = 0;
    my_stpcpy(pager, pager_name);
    my_stpcpy(default_pager, pager_name);
  }
  opt_nopager = false;
  tee_fprintf(stdout, "PAGER set to '%s'\n", pager);
  return 0;
}

static int com_nopager(String *buffer [[maybe_unused]],
                       char *line [[maybe_unused]]) {
  my_stpcpy(pager, "stdout");
  opt_nopager = true;
  PAGER = stdout;
  tee_fprintf(stdout, "PAGER set to stdout\n");
  return 0;
}
#endif

/*
  Sorry, you can't send the result to an editor in Win32
*/

#ifdef USE_POPEN
static int com_edit(String *buffer, char *line [[maybe_unused]]) {
  char filename[FN_REFLEN], buff[160];
  int fd, tmp;
  const char *editor;

  fd = create_temp_file(filename, NullS, "sql", O_CREAT | O_WRONLY, KEEP_FILE,
                        MYF(MY_WME));
  if (fd < 0) goto err;
  if (buffer->is_empty() && !old_buffer.is_empty())
    (void)my_write(fd, (uchar *)old_buffer.ptr(), old_buffer.length(),
                   MYF(MY_WME));
  else
    (void)my_write(fd, (uchar *)buffer->ptr(), buffer->length(), MYF(MY_WME));
  (void)my_close(fd, MYF(0));

  if (!(editor = (char *)getenv("EDITOR")) &&
      !(editor = (char *)getenv("VISUAL")))
    editor = "vi";
  strxmov(buff, editor, " ", filename, NullS);
  if (system(buff) == -1) goto err;

  MY_STAT stat_arg;
  if (!my_stat(filename, &stat_arg, MYF(MY_WME))) goto err;
  fd = my_open(filename, O_RDONLY, MYF(MY_WME));
  if (fd < 0) goto err;
  (void)buffer->alloc((uint)stat_arg.st_size);
  tmp = read(fd, buffer->ptr(), buffer->alloced_length());
  if (tmp >= 0L)
    buffer->length((uint)tmp);
  else
    buffer->length(0);
  (void)my_close(fd, MYF(0));
  (void)my_delete(filename, MYF(MY_WME));
err:
  return 0;
}
#endif

/* If arg is given, exit without errors. This happens on command 'quit' */

static int com_quit(String *buffer [[maybe_unused]],
                    char *line [[maybe_unused]]) {
  status.exit_status = 0;
  return 1;
}

static int com_rehash(String *buffer [[maybe_unused]],
                      char *line [[maybe_unused]]) {
#ifdef HAVE_READLINE
  build_completion_hash(true, false);
#endif
  return 0;
}

static int com_shell(String *buffer [[maybe_unused]],
                     char *line [[maybe_unused]]) {
  char *shell_cmd;

  /* Skip space from line begin */
  while (my_isspace(charset_info, *line)) line++;
  shell_cmd = strchr(line, ' ');
  if (!shell_cmd) {
    put_info("Usage: \\! shell-command", INFO_ERROR);
    return -1;
  }

  if (!opt_system_command) {
    return put_info(
        "'system' command received, but the --system-command option is off. "
        "Skipping.",
        INFO_ERROR);
  }
  /*
    The output of the shell command does not
    get directed to the pager or the outfile
  */
  if (system(shell_cmd) == -1) {
    put_info(strerror(errno), INFO_ERROR, errno);
    return -1;
  }
  return 0;
}

static int com_print(String *buffer, char *line [[maybe_unused]]) {
  tee_puts("--------------", stdout);
  (void)tee_fputs(buffer->c_ptr(), stdout);
  if (!buffer->length() || (*buffer)[buffer->length() - 1] != '\n')
    tee_putc('\n', stdout);
  tee_puts("--------------\n", stdout);
  return 0; /* If empty buffer */
}

/* ARGSUSED */
static int com_connect(String *buffer, char *line) {
  char *tmp, buff[256];
  const bool save_rehash = opt_rehash;
  int error;

  memset(buff, 0, sizeof(buff));
  if (buffer) {
    /*
      Two null bytes are needed in the end of buff to allow
      get_arg to find end of string the second time it's called.
    */
    tmp = strmake(buff, line, sizeof(buff) - 2);
#ifdef EXTRA_DEBUG
    tmp[1] = 0;
#endif
    tmp = get_arg(buff, false);
    if (tmp && *tmp) {
      my_free(current_db);
      current_db = my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME));
      tmp = get_arg(buff, true);
      if (tmp) {
        my_free(current_host);
        current_host = my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME));
        my_free(dns_srv_name);
        dns_srv_name = nullptr;
      }
    } else {
      /* Quick re-connect */
      opt_rehash = false; /* purecov: tested */
    }
    buffer->length(0);  // command used
  } else
    opt_rehash = false;
  error = sql_connect(current_host, current_db, current_user, 0);
  opt_rehash = save_rehash;

  if (connected) {
    sprintf(buff, "Connection id:    %lu", mysql_thread_id(&mysql_handle));
    put_info(buff, INFO_INFO);
    sprintf(buff, "Current database: %.128s\n",
            current_db ? current_db : "*** NONE ***");
    put_info(buff, INFO_INFO);
  }
  return error;
}

static int com_source(String *buffer [[maybe_unused]], char *line) {
  char source_name[FN_REFLEN], *end, *param;
  LINE_BUFFER *line_buff;
  int error;
  STATUS old_status;
  FILE *sql_file;

  /* Skip space from file name */
  while (my_isspace(charset_info, *line)) line++;
  param = strchr(line, ' ');
  if (!param)  // Skip command name
    return put_info("Usage: \\. <filename> | source <filename>", INFO_ERROR, 0);
  while (my_isspace(charset_info, *param)) param++;
  end = strmake(source_name, param, sizeof(source_name) - 1);
  while (end > source_name && (my_isspace(charset_info, end[-1]) ||
                               my_iscntrl(charset_info, end[-1])))
    end--;
  end[0] = 0;
  unpack_filename(source_name, source_name);
  /* open file name */
  sql_file = my_fopen(source_name, O_RDONLY | MY_FOPEN_BINARY, MYF(0));
  if (!sql_file) {
    char buff[FN_REFLEN + 60];
    sprintf(buff, "Failed to open file '%s', error: %d", source_name, errno);
    return put_info(buff, INFO_ERROR, 0);
  }

  line_buff = batch_readline_init(MAX_BATCH_BUFFER_SIZE, sql_file);
  if (!line_buff) {
    my_fclose(sql_file, MYF(0));
    return put_info("Can't initialize batch_readline", INFO_ERROR, 0);
  }

  /* Save old status */
  old_status = status;
  memset(&status, 0, sizeof(status));

  status.batch = old_status.batch;  // Run in batch mode
  status.line_buff = line_buff;
  status.file_name = source_name;
  glob_buffer.length(0);  // Empty command buffer
  error = read_and_execute(false);
  status = old_status;  // Continue as before
  my_fclose(sql_file, MYF(0));
  batch_readline_end(line_buff);
  return error;
}

/* ARGSUSED */
static int com_delimiter(String *buffer [[maybe_unused]], char *line) {
  char buff[256], *tmp;

  strmake(buff, line, sizeof(buff) - 1);
  tmp = get_arg(buff, false);

  if (!tmp || !*tmp) {
    put_info("DELIMITER must be followed by a 'delimiter' character or string",
             INFO_ERROR);
    return 0;
  }
  if (strstr(tmp, "\\")) {
    put_info("DELIMITER cannot contain a backslash character", INFO_ERROR);
    return 0;
  }

  strmake(delimiter, tmp, sizeof(delimiter) - 1);
  delimiter_length = (int)strlen(delimiter);
  delimiter_str = delimiter;
  return 0;
}

/* ARGSUSED */
static int com_use(String *buffer [[maybe_unused]], char *line) {
  char *tmp, buff[FN_REFLEN + 1];
  int select_db;
  uint warnings;

  memset(buff, 0, sizeof(buff));

  /*
    In case of quotes used, try to get the normalized db name.
  */
  if (get_quote_count(line) > 0) {
    if (normalize_dbname(line, buff, sizeof(buff)))
      return put_error(&mysql_handle);
    tmp = buff;
  } else {
    strmake(buff, line, sizeof(buff) - 1);
    tmp = get_arg(buff, false);
  }

  if (!tmp || !*tmp) {
    put_info("USE must be followed by a database name", INFO_ERROR);
    return 0;
  }
  /*
    We need to recheck the current database, because it may change
    under our feet, for example if DROP DATABASE or RENAME DATABASE
    (latter one not yet available by the time the comment was written)
    If this command fails we assume we lost connection.
  */
  if (get_current_db()) connected = false;

  if (!current_db || cmp_database(charset_info, current_db, tmp) != 0) {
    if (one_database) {
      skip_updates = true;
      select_db = 0;  // don't do mysql_select_db()
    } else
      select_db = 2;  // do mysql_select_db() and build_completion_hash()
  } else {
    /*
      USE to the current db specified.
      We do need to send mysql_select_db() to make server
      update database level privileges, which might
      change since last USE (see bug#10979).
      For performance purposes, we'll skip rebuilding of completion hash.
    */
    skip_updates = false;
    select_db = 1;  // do only mysql_select_db(), without completion
  }

  if (select_db) {
    /*
      reconnect once if connection is down or if connection was found to
      be down during query
    */
    if (!connected && reconnect())
      return opt_reconnect ? -1 : 1;  // Fatal error
    if (mysql_select_db(&mysql_handle, tmp)) {
      if (mysql_errno(&mysql_handle) != CR_SERVER_GONE_ERROR)
        return put_error(&mysql_handle);

      if (reconnect()) return opt_reconnect ? -1 : 1;  // Fatal error
      if (mysql_select_db(&mysql_handle, tmp)) return put_error(&mysql_handle);
    }
    my_free(current_db);
    current_db = my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME));
#ifdef HAVE_READLINE
    if (select_db > 1) build_completion_hash(opt_rehash, true);
#endif
  }

  warnings = mysql_warning_count(&mysql_handle);
  if (warnings > 0) {
    snprintf(buff, sizeof(buff), "Database changed, %u warning%s", warnings,
             warnings > 1 ? "s" : "");
    put_info(buff, INFO_INFO);
    if (show_warnings == 1) print_warnings();
  } else
    put_info("Database changed", INFO_INFO);
  return 0;
}

/**
  Normalize database name.

  @param [in] line           The command.
  @param [out] buff          Normalized db name.
  @param [in] buff_size      Buffer size.

  @return Operation status
      @retval 0    Success
      @retval 1    Failure

  @note Sometimes server normalizes the database names
        & APIs like mysql_select_db() expect normalized
        database names. Since it is difficult to perform
        the name conversion/normalization on the client
        side, this function tries to get the normalized
        dbname (indirectly) from the server.
*/

static int normalize_dbname(const char *line, char *buff, uint buff_size) {
  MYSQL_RES *res = nullptr;

  /* Send the "USE db" command to the server. */
  if (mysql_query(&mysql_handle, line)) return 1;

  /*
    Now, get the normalized database name and store it
    into the buff.
  */
  if (!mysql_query(&mysql_handle, "SELECT DATABASE()") &&
      (res = mysql_use_result(&mysql_handle))) {
    MYSQL_ROW row = mysql_fetch_row(res);
    if (row && row[0]) {
      const size_t len = strlen(row[0]);
      /* Make sure there is enough room to store the dbname. */
      if ((len > buff_size) || !memcpy(buff, row[0], len)) {
        mysql_free_result(res);
        return 1;
      }
    }
    mysql_free_result(res);
  }

  /* Restore the original database. */
  if (current_db && mysql_select_db(&mysql_handle, current_db)) return 1;

  return 0;
}

static int com_warnings(String *buffer [[maybe_unused]],
                        char *line [[maybe_unused]]) {
  show_warnings = true;
  put_info("Show warnings enabled.", INFO_INFO);
  return 0;
}

static int com_nowarnings(String *buffer [[maybe_unused]],
                          char *line [[maybe_unused]]) {
  show_warnings = false;
  put_info("Show warnings disabled.", INFO_INFO);
  return 0;
}

static int com_query_attributes(String *buffer [[maybe_unused]], char *line) {
  char buff[1024], *param, name[1024];
  memset(buff, 0, sizeof(buff));
  strmake(buff, line, sizeof(buff) - 1);
  param = buff;
  global_attrs->clear(connected ? &mysql_handle : nullptr);
  do {
    param = get_arg(param, param != buff);
    if (!param || !*param) break;

    strncpy(name, param, sizeof(name) - 1);
    param = get_arg(param, true);
    if (!param || !*param) {
      return put_info("Usage: query_attributes name1 value1 name2 value2 ...",
                      INFO_ERROR, 0);
    }

    if (global_attrs->push_param(name, param))
      return put_info("Failed to push a parameter", INFO_ERROR, 0);
  } while (param != nullptr);
  return 0;
}

static int com_ssl_session_data_print(String *buffer [[maybe_unused]],
                                      char *line) {
  char msgbuf[256];
  char *param = get_arg(line, false);
  const char *err_text = nullptr;
  File fo = -1;
  char *data = nullptr;
  const bool use_outfile = (param != nullptr);

  if (use_outfile) {
    // result file is access protected (0600)
    const MY_MODE file_creation_mode = get_file_perm(USER_READ | USER_WRITE);
    const int access_flags = O_WRONLY | O_TRUNC | O_CREAT;

    fo = my_create(param, file_creation_mode, access_flags, MYF(0));
    if (fo == -1) {
      err_text = "Failed to open the output file";
      goto end;
    }
  } else {
    fo = my_fileno(stdout);
  }

  data = reinterpret_cast<char *>(
      mysql_get_ssl_session_data(&mysql_handle, 0, nullptr));
  if (!data) {
    err_text = nullptr;
    put_error(&mysql_handle);
    goto end;
  }
  if (my_write(fo, (uchar *)data, strlen(data), MYF(0)) == MY_FILE_ERROR) {
    snprintf(msgbuf, sizeof(msgbuf), "Write of session data failed: %d (%s)",
             errno, strerror(errno));
    err_text = &msgbuf[0];
    goto end;
  }
  if (!use_outfile && fo != -1) my_write(fo, (const uchar *)"\n", 1, MYF(0));

end:
  if (data) mysql_free_ssl_session_data(&mysql_handle, data);
  if (use_outfile && fo != -1) (void)my_close(fo, MYF(0));
  if (err_text) return put_info(err_text, INFO_ERROR);
  return 0;
}

/*
  Gets argument from a command on the command line. If get_next_arg is
  not defined, skips the command and returns the first argument. The
  line is modified by adding zero to the end of the argument. If
  get_next_arg is defined, then the function searches for end of string
  first, after found, returns the next argument and adds zero to the
  end. If you ever wish to use this feature, remember to initialize all
  items in the array to zero first.
*/

char *get_arg(char *line, bool get_next_arg) {
  char *ptr, *start;
  bool quoted = false, valid_arg = false;
  char qtype = 0;

  ptr = line;
  if (get_next_arg) {
    for (; *ptr; ptr++)
      ;
    if (*(ptr + 1)) ptr++;
  } else {
    /* skip leading white spaces */
    while (my_isspace(charset_info, *ptr)) ptr++;
    if (*ptr == '\\')  // short command was used
      ptr += 2;
    else
      while (*ptr && !my_isspace(charset_info, *ptr))  // skip command
        ptr++;
  }
  if (!*ptr) return NullS;
  while (my_isspace(charset_info, *ptr)) ptr++;
  if (*ptr == '\'' || *ptr == '\"' || *ptr == '`') {
    qtype = *ptr;
    quoted = true;
    ptr++;
  }
  for (start = ptr; *ptr; ptr++) {
    // if it is a quoted string do not remove backslash
    if (!quoted && *ptr == '\\' && ptr[1])  // escaped character
    {
      // Remove the backslash
      my_stpmov(ptr, ptr + 1);
    } else if ((!quoted && *ptr == ' ') || (quoted && *ptr == qtype)) {
      *ptr = 0;
      break;
    }
  }
  valid_arg = ptr != start;
  return valid_arg ? start : NullS;
}

/*
  Number of quotes present in the command's argument.
*/
static int get_quote_count(const char *line) {
  int quote_count = 0;
  const char *quote = line;

  while ((quote = strpbrk(quote, "'`\"")) != nullptr) {
    quote_count++;
    quote++;
  }

  return quote_count;
}

static int sql_real_connect(char *host, char *database, char *user, char *,
                            uint silent) {
  if (connected) {
    connected = false;
#ifdef HAVE_SETNS
    if (opt_network_namespace) (void)release_network_namespace_resources();
#endif
    mysql_close(&mysql_handle);
  }

  mysql_init(&mysql_handle);
  if (init_connection_options(&mysql_handle)) {
    put_error_if_any(&mysql_handle);
    (void)fflush(stdout);
    return ignore_errors ? -1 : 1;
  }
#ifdef _WIN32
  uint cnv_errors;
  String converted_database, converted_user;
  if (!my_charset_same(&my_charset_utf8mb4_bin, mysql_handle.charset)) {
    /* Convert user and database from UTF8MB4 to connection character set */
    if (user) {
      converted_user.copy(user, strlen(user) + 1, &my_charset_utf8mb4_bin,
                          mysql_handle.charset, &cnv_errors);
      user = (char *)converted_user.ptr();
    }
    if (database) {
      converted_database.copy(database, strlen(database) + 1,
                              &my_charset_utf8mb4_bin, mysql_handle.charset,
                              &cnv_errors);
      database = (char *)converted_database.ptr();
    }
  }
#endif

#ifdef HAVE_SETNS
  if (opt_network_namespace && set_network_namespace(opt_network_namespace)) {
    if (!silent) {
      char msgbuf[PATH_MAX];
      snprintf(msgbuf, sizeof(msgbuf), "Network namespace error: %s",
               strerror(errno));
      put_info(msgbuf, INFO_ERROR);
    }

    return ignore_errors ? -1 : 1;  // Abort
  }
#endif
  MYSQL *ret;
  if (dns_srv_name)
    ret = mysql_real_connect_dns_srv(&mysql_handle, dns_srv_name, user, nullptr,
                                     database,
                                     connect_flag | CLIENT_MULTI_STATEMENTS);
  else
    ret = mysql_real_connect(&mysql_handle, host, user, nullptr, database,
                             opt_mysql_port, opt_mysql_unix_port,
                             connect_flag | CLIENT_MULTI_STATEMENTS);
  if (!ret) {
#ifdef HAVE_SETNS
    if (opt_network_namespace) (void)restore_original_network_namespace();
#endif
    if (mysql_errno(&mysql_handle) == ER_MUST_CHANGE_PASSWORD_LOGIN) {
      tee_fprintf(stdout,
                  "Please use --connect-expired-password option or "
                  "invoke mysql in interactive mode.\n");
      return ignore_errors ? -1 : 1;
    }
    if (!silent || (mysql_errno(&mysql_handle) != CR_CONN_HOST_ERROR &&
                    mysql_errno(&mysql_handle) != CR_CONNECTION_ERROR)) {
      (void)put_error(&mysql_handle);
      (void)fflush(stdout);
      return ignore_errors ? -1 : 1;  // Abort
    }
    return -1;  // Retryable
  }

  /* do token device registration */
  if (opt_register_factor) {
    char errmsg[FN_REFLEN + 1]{0};
    if (user_device_registration(&mysql_handle, opt_register_factor, errmsg)) {
      put_info(errmsg, INFO_ERROR);
      return 1;
    }
  }

#ifdef HAVE_SETNS
  if (opt_network_namespace && restore_original_network_namespace()) {
    if (!silent) {
      char msgbuf[PATH_MAX];
      snprintf(msgbuf, sizeof(msgbuf), "Network namespace error: %s",
               strerror(errno));
      put_info(msgbuf, INFO_ERROR);
    }

    return ignore_errors ? -1 : 1;  // Abort
  }
#endif

  if (ssl_client_check_post_connect_ssl_setup(
          &mysql_handle, [](const char *err) { put_info(err, INFO_ERROR); }))
    return 1;

  void *new_ssl_session_data =
      mysql_get_ssl_session_data(&mysql_handle, 0, nullptr);
  if (new_ssl_session_data != nullptr) {
    if (ssl_session_data != nullptr)
      mysql_free_ssl_session_data(&mysql_handle, ssl_session_data);
    ssl_session_data = new_ssl_session_data;
  } else {
    DBUG_PRINT("error", ("unable to save SSL session"));
  }
#ifdef _WIN32
  /* Convert --execute buffer from UTF8MB4 to connection character set */
  if (!execute_buffer_conversion_done && status.line_buff &&
      !status.line_buff->file && /* Convert only -e buffer, not real file */
      status.line_buff->buffer < status.line_buff->end && /* Non-empty */
      !my_charset_same(&my_charset_utf8mb4_bin, mysql_handle.charset)) {
    String tmp;
    size_t len = status.line_buff->end - status.line_buff->buffer;
    uint dummy_errors;
    /*
      Don't convert trailing '\n' character - it was appended during
      last batch_readline_command() call.
      Otherwise we'll get an extra line, which makes some tests fail.
    */
    if (status.line_buff->buffer[len - 1] == '\n') len--;
    if (tmp.copy(status.line_buff->buffer, len, &my_charset_utf8mb4_bin,
                 mysql_handle.charset, &dummy_errors))
      return 1;

    /* Free the old line buffer */
    batch_readline_end(status.line_buff);

    /* Re-initialize line buffer from the converted string */
    status.line_buff = batch_readline_command(nullptr, tmp.c_ptr_safe());
    if (!status.line_buff) return 1;
  }
  execute_buffer_conversion_done = true;
#endif /* _WIN32 */

  charset_info = mysql_handle.charset;

  connected = true;
#ifdef HAVE_READLINE
  build_completion_hash(opt_rehash, true);
#endif
  return 0;
}

/* Initialize options for the given connection handle. */
static bool init_connection_options(MYSQL *mysql) {
  bool handle_expired = (opt_connect_expired_password || !status.batch);

  opt_init_commands.set_mysql_options(mysql, MYSQL_INIT_COMMAND);

  if (opt_connect_timeout) {
    uint timeout = opt_connect_timeout;
    mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, (char *)&timeout);
  }

  if (opt_bind_addr) mysql_options(mysql, MYSQL_OPT_BIND, opt_bind_addr);

  if (opt_compress) mysql_options(mysql, MYSQL_OPT_COMPRESS, NullS);
  if (opt_compress_algorithm)
    mysql_options(mysql, MYSQL_OPT_COMPRESSION_ALGORITHMS,
                  opt_compress_algorithm);

  mysql_options(mysql, MYSQL_OPT_ZSTD_COMPRESSION_LEVEL,
                &opt_zstd_compress_level);

  if (using_opt_local_infile)
    mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, (char *)&opt_local_infile);

  if (SSL_SET_OPTIONS(mysql)) {
    tee_fprintf(stdout, "%s", SSL_SET_OPTIONS_ERROR);
    return true;
  }

  if (ssl_session_data)
    mysql_options(mysql, MYSQL_OPT_SSL_SESSION_DATA, ssl_session_data);

  if (opt_protocol)
    mysql_options(mysql, MYSQL_OPT_PROTOCOL, (char *)&opt_protocol);

#if defined(_WIN32)
  if (shared_memory_base_name)
    mysql_options(mysql, MYSQL_SHARED_MEMORY_BASE_NAME,
                  shared_memory_base_name);
#endif

  if (safe_updates) {
    char init_command[100];
    sprintf(init_command,
            "SET SQL_SAFE_UPDATES=1,SQL_SELECT_LIMIT=%lu,MAX_JOIN_SIZE=%lu",
            select_limit, max_join_size);
    mysql_options(mysql, MYSQL_INIT_COMMAND, init_command);
  }

  if (mysql_set_character_set(mysql, default_charset)) return true;

  if (opt_plugin_dir && *opt_plugin_dir)
    mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir);

  if (opt_load_data_local_dir &&
      mysql_options(mysql, MYSQL_OPT_LOAD_DATA_LOCAL_DIR,
                    opt_load_data_local_dir))
    return true;

  if (opt_default_auth && *opt_default_auth)
    mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth);

  set_server_public_key(mysql);

  set_get_server_public_key_option(mysql);

  if (using_opt_enable_cleartext_plugin)
    mysql_options(mysql, MYSQL_ENABLE_CLEARTEXT_PLUGIN,
                  (char *)&opt_enable_cleartext_plugin);

  mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, nullptr);
  mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysql");
  if (current_os_user)
    mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "os_user",
                   current_os_user);
  if (current_os_sudouser)
    mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, "os_sudouser",
                   current_os_sudouser);

  mysql_options(mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &handle_expired);

  set_password_options(mysql);

  struct st_mysql_client_plugin *oci_iam_plugin = mysql_client_find_plugin(
      mysql, "authentication_oci_client", MYSQL_CLIENT_AUTHENTICATION_PLUGIN);

  /* set authentication_oci_client config profile option if required */
  if (opt_authentication_oci_client_config_profile != nullptr) {
    if (!oci_iam_plugin) {
      put_info("Cannot load the authentication_oci_client plugin.", INFO_ERROR);
      return true;
    }
    if (mysql_plugin_options(oci_iam_plugin,
                             "authentication-oci-client-config-profile",
                             opt_authentication_oci_client_config_profile)) {
      put_info(
          "Failed to set config profile for authentication_oci_client "
          "plugin.",
          INFO_ERROR);
      return true;
    }
  }
  /* set OCI config file option if required */
  if (opt_oci_config_file != nullptr) {
    if (!oci_iam_plugin) {
      put_info("Cannot load the authentication_oci_client plugin.", INFO_ERROR);
      return true;
    }
    if (mysql_plugin_options(oci_iam_plugin, "oci-config-file",
                             opt_oci_config_file)) {
      put_info(
          "Failed to set config file for authentication_oci_client plugin.",
          INFO_ERROR);
      return true;
    }
  }

  /* set authentication_openid_connect_client ID token file option if required
   */
  if (opt_authentication_openid_connect_client_id_token_file != nullptr) {
    struct st_mysql_client_plugin *openid_connect_plugin =
        mysql_client_find_plugin(mysql, "authentication_openid_connect_client",
                                 MYSQL_CLIENT_AUTHENTICATION_PLUGIN);
    if (!openid_connect_plugin) {
      put_info("Cannot load the authentication_openid_connect_client plugin.",
               INFO_ERROR);
      return true;
    }
    if (mysql_plugin_options(
            openid_connect_plugin, "id-token-file",
            opt_authentication_openid_connect_client_id_token_file)) {
      put_info(
          "Failed to set id token file for "
          "authentication_openid_connect_client "
          "plugin.",
          INFO_ERROR);
      return true;
    }
  }

  char error[256]{0};
#if defined(_WIN32)
  if (set_authentication_kerberos_client_mode(mysql, error, 255)) {
    put_info(error, INFO_ERROR);
    return 1;
  }
#endif
  if (set_authentication_webauthn_options(mysql, error, 255)) {
    put_info(error, INFO_ERROR);
    return true;
  }

  return false;
}

static int sql_connect(char *host, char *database, char *user, uint silent) {
  bool message = false;
  uint count = 0;
  int error;
  for (;;) {
    error = sql_real_connect(host, database, user, nullptr, wait_flag);
    if (error >= 0) {
      if (count) {
        tee_fputs("\n", stderr);
        (void)fflush(stderr);
      }
      return error;
    }
    // exit after single try if no --wait flag
    if (!wait_flag) return ignore_errors ? -1 : 1;
    // exit after two tries if --wait, but no --force flag provided
    if (!ignore_errors && count > 0) {
      tee_fputs("\n", stderr);
      (void)fflush(stderr);
      return 1;
    }

    if (!message && (!silent || verbose)) {
      message = true;
      tee_fputs("Waiting", stderr);
      (void)fflush(stderr);
    }
    (void)sleep(wait_time);
    if (!silent) {
      putc('.', stderr);
      (void)fflush(stderr);
    }
    count++;
  }
}

static int com_status(String *buffer [[maybe_unused]],
                      char *line [[maybe_unused]]) {
  const char *status_str;
  char buff[40];
  ulonglong id;
  MYSQL_RES *result = nullptr;

  if (mysql_real_query_for_lazy(
          STRING_WITH_LEN("select DATABASE(), USER() limit 1")))
    return 0;

  tee_puts("--------------", stdout);
  usage(1); /* Print version */
  tee_fprintf(stdout, "\nConnection id:\t\t%lu\n",
              mysql_thread_id(&mysql_handle));
  /*
    Don't remove "limit 1",
    it is protection against SQL_SELECT_LIMIT=0
  */
  if (!mysql_store_result_for_lazy(&result)) {
    MYSQL_ROW cur = mysql_fetch_row(result);
    if (cur) {
      tee_fprintf(stdout, "Current database:\t%s\n", cur[0] ? cur[0] : "");
      tee_fprintf(stdout, "Current user:\t\t%s\n", cur[1]);
    }
    mysql_free_result(result);
  }

  status_str = mysql_get_ssl_cipher(&mysql_handle);
  if (status_str)
    tee_fprintf(stdout, "SSL:\t\t\tCipher in use is %s\n", status_str);
  else
    tee_puts("SSL:\t\t\tNot in use", stdout);

  if (skip_updates) {
    tee_fprintf(stdout, "\nAll updates ignored to this database\n");
  }
#ifdef USE_POPEN
  tee_fprintf(stdout, "Current pager:\t\t%s\n", pager);
  tee_fprintf(stdout, "Using outfile:\t\t'%s'\n", opt_outfile ? outfile : "");
#endif
  tee_fprintf(stdout, "Using delimiter:\t%s\n", delimiter);
  tee_fprintf(stdout, "Server version:\t\t%s\n",
              server_version_string(&mysql_handle));
  tee_fprintf(stdout, "Protocol version:\t%d\n",
              mysql_get_proto_info(&mysql_handle));
  tee_fprintf(stdout, "Connection:\t\t%s\n",
              mysql_get_host_info(&mysql_handle));
  id = mysql_insert_id(&mysql_handle);
  if (id > 0) tee_fprintf(stdout, "Insert id:\t\t%s\n", llstr(id, buff));

  /* "limit 1" is protection against SQL_SELECT_LIMIT=0 */
  if (mysql_real_query_for_lazy(STRING_WITH_LEN(
          "select @@character_set_client, @@character_set_connection, "
          "@@character_set_server, @@character_set_database limit 1"))) {
    if (mysql_errno(&mysql_handle) == CR_SERVER_GONE_ERROR) return 0;
  }
  if (!mysql_store_result_for_lazy(&result)) {
    MYSQL_ROW cur = mysql_fetch_row(result);
    if (cur) {
      tee_fprintf(stdout, "Server characterset:\t%s\n", cur[2] ? cur[2] : "");
      tee_fprintf(stdout, "Db     characterset:\t%s\n", cur[3] ? cur[3] : "");
      tee_fprintf(stdout, "Client characterset:\t%s\n", cur[0] ? cur[0] : "");
      tee_fprintf(stdout, "Conn.  characterset:\t%s\n", cur[1] ? cur[1] : "");
    }
    mysql_free_result(result);
  } else {
    /* Probably pre-4.1 server */
    tee_fprintf(stdout, "Client characterset:\t%s\n", charset_info->csname);
    tee_fprintf(stdout, "Server characterset:\t%s\n",
                mysql_handle.charset->csname);
  }

  if (strstr(mysql_get_host_info(&mysql_handle), "TCP/IP") ||
      !mysql_handle.unix_socket)
    tee_fprintf(stdout, "TCP port:\t\t%d\n", mysql_handle.port);
  else
    tee_fprintf(stdout, "UNIX socket:\t\t%s\n", mysql_handle.unix_socket);
  if (mysql_handle.net.compress)
    tee_fprintf(stdout,
                "Protocol:\t\tCompressed, algorithms: %s, zstd level: %d\n",
                opt_compress_algorithm, opt_zstd_compress_level);
  if (opt_binhex) tee_fprintf(stdout, "Binary data as:\t\tHexadecimal\n");
  if (mysql_get_ssl_session_reused(&mysql_handle))
    tee_fprintf(stdout, "SSL session reused:\ttrue\n");

  status_str = mysql_stat(&mysql_handle);
  if (status_str && !mysql_error(&mysql_handle)[0]) {
    ulong sec;
    const char *pos = strchr(status_str, ' ');
    /* print label */
    tee_fprintf(stdout, "%.*s\t\t\t", (int)(pos - status_str), status_str);
    status_str = str2int(pos, 10, 0, LONG_MAX, (long *)&sec);
    if (status_str) {
      nice_time((double)sec, buff, false);
      tee_puts(buff, stdout);                  /* print nice time */
      while (*status_str == ' ') status_str++; /* to next info */
      tee_putc('\n', stdout);
      tee_puts(status_str, stdout);
    }
  }
  if (safe_updates) {
    tee_fprintf(stdout, "\nNote that you are running in safe_update_mode:\n");
    tee_fprintf(stdout,
                "\
UPDATEs and DELETEs that don't use a key in the WHERE clause are not allowed.\n\
(One can force an UPDATE/DELETE by adding LIMIT # at the end of the command.)\n\
SELECT has an automatic 'LIMIT %lu' if LIMIT is not used.\n\
Max number of examined row combination in a join is set to: %lu\n\n",
                select_limit, max_join_size);
  }
  tee_puts("--------------\n", stdout);
  return 0;
}

static const char *server_version_string(MYSQL *con) {
  /* Only one thread calls this, so no synchronization is needed */
  if (server_version == nullptr) {
    MYSQL_RES *result;

    /* "limit 1" is protection against SQL_SELECT_LIMIT=0 */
    if (!mysql_query(con, "select @@version_comment limit 1") &&
        (result = mysql_use_result(con))) {
      MYSQL_ROW cur = mysql_fetch_row(result);
      if (cur && cur[0]) {
        /* version, space, comment, \0 */
        const size_t len =
            strlen(mysql_get_server_info(con)) + strlen(cur[0]) + 2;

        server_version =
            (char *)my_malloc(PSI_NOT_INSTRUMENTED, len, MYF(MY_WME));
        if (server_version) {
          char *bufp;
          bufp = my_stpcpy(server_version, mysql_get_server_info(con));
          bufp = my_stpcpy(bufp, " ");
          (void)my_stpcpy(bufp, cur[0]);
        }
      }
      mysql_free_result(result);
    }

    /*
      If for some reason we didn't get a version_comment, we'll
      keep things simple.
    */

    if (server_version == nullptr)
      server_version = my_strdup(PSI_NOT_INSTRUMENTED,
                                 mysql_get_server_info(con), MYF(MY_WME));
  }

  return server_version ? server_version : "";
}

static int put_info(const char *str, INFO_TYPE info_type, uint error,
                    const char *sqlstate) {
  FILE *file = (info_type == INFO_ERROR ? stderr : stdout);
  static int inited = 0;

  if (status.batch) {
    if (info_type == INFO_ERROR) {
      (void)fflush(stdout);  // flush stdout before stderr
      (void)fflush(file);
      fprintf(file, "ERROR");
      if (error) {
        if (sqlstate)
          (void)fprintf(file, " %d (%s)", error, sqlstate);
        else
          (void)fprintf(file, " %d", error);
      }
      if (status.query_start_line && line_numbers) {
        (void)fprintf(file, " at line %lu", status.query_start_line);
        if (status.file_name)
          (void)fprintf(file, " in file: '%s'", status.file_name);
      }
      (void)fprintf(file, ": %s\n", str);
      (void)fflush(file);
      if (!ignore_errors) return 1;
    } else if (info_type == INFO_RESULT && verbose > 1)
      tee_puts(str, file);
    if (unbuffered) fflush(file);
    return info_type == INFO_ERROR ? -1 : 0;
  }
  if (!opt_silent || info_type == INFO_ERROR) {
    if (!inited) {
      inited = 1;
    }
    if (info_type == INFO_ERROR) {
      if (!opt_nobeep) putchar('\a'); /* This should make a bell */
      if (error) {
        if (sqlstate)
          (void)tee_fprintf(file, "ERROR %d (%s): ", error, sqlstate);
        else
          (void)tee_fprintf(file, "ERROR %d: ", error);
      } else
        tee_puts("ERROR: ", file);
    }
    (void)tee_puts(str, file);
  }
  if (unbuffered) fflush(file);
  return info_type == INFO_ERROR ? -1 : 0;
}

static int put_error(MYSQL *con) {
  return put_info(mysql_error(con), INFO_ERROR, mysql_errno(con),
                  mysql_sqlstate(con));
}

/**
 Prints the SQL error, if any

 Similar to @ref put_error, but prints the error only if there is any.

 @param con the connection to check for errors
*/
static void put_error_if_any(MYSQL *con) {
  const char *err = mysql_error(con);
  if (err && *err)
    put_info(err, INFO_ERROR, mysql_errno(con), mysql_sqlstate(con));
}

static void remove_cntrl(String *buffer) {
  const char *start = buffer->ptr();
  const char *end = start + buffer->length();
  while (start < end && !my_isgraph(charset_info, end[-1])) end--;
  buffer->length((uint)(end - start));
}

/**
  Write data to a stream.
  Various modes, corresponding to --tab, --xml, --raw parameters,
  are supported.

  @param file   Stream to write to
  @param s      String to write
  @param slen   String length
  @param flags  Flags for --tab, --xml, --raw.
*/
void tee_write(FILE *file, const char *s, size_t slen, int flags) {
#ifdef _WIN32
  const bool is_console = my_win_is_console_cached(file);
#endif
  const bool is_mb = use_mb(charset_info);
  const char *se;
  for (se = s + slen; s < se; s++) {
    const char *t;

    if (flags & MY_PRINT_MB) {
      int mblen;
      if (is_mb && (mblen = my_ismbchar(charset_info, s, se))) {
#ifdef _WIN32
        if (is_console)
          my_win_console_write(charset_info, s, mblen);
        else
#endif
            if (fwrite(s, 1, mblen, file) != (size_t)mblen) {
          perror("fwrite");
        }
        if (opt_outfile) {
          if (fwrite(s, 1, mblen, OUTFILE) != (size_t)mblen) {
            perror("fwrite");
          }
        }
        s += mblen - 1;
        continue;
      }
    }

    if ((flags & MY_PRINT_XML) && (t = array_value(xmlmeta, *s)))
      tee_fputs(t, file);
    else if ((flags & MY_PRINT_SPS_0) && *s == '\0')
      tee_putc((int)' ', file);  // This makes everything hard
    else if ((flags & MY_PRINT_ESC_0) && *s == '\0')
      tee_fputs("\\0", file);  // This makes everything hard
    else if ((flags & MY_PRINT_CTRL) && *s == '\t')
      tee_fputs("\\t", file);  // This would destroy tab format
    else if ((flags & MY_PRINT_CTRL) && *s == '\n')
      tee_fputs("\\n", file);  // This too
    else if ((flags & MY_PRINT_CTRL) && *s == '\\')
      tee_fputs("\\\\", file);
    else {
#ifdef _WIN32
      if (is_console)
        my_win_console_putc(charset_info, (int)*s);
      else
#endif
        putc((int)*s, file);
      if (opt_outfile) putc((int)*s, OUTFILE);
    }
  }
}

void tee_fprintf(FILE *file, const char *fmt, ...) {
  va_list args;

  va_start(args, fmt);
#ifdef _WIN32
  if (my_win_is_console_cached(file))
    my_win_console_vfprintf(charset_info, fmt, args);
  else
#endif
    (void)vfprintf(file, fmt, args);
  va_end(args);

  if (opt_outfile) {
    va_start(args, fmt);
    (void)vfprintf(OUTFILE, fmt, args);
    va_end(args);
  }
}

/*
  Write a 0-terminated string to file and OUTFILE.
  TODO: possibly it's nice to have a version with length some day,
  e.g. tee_fnputs(s, slen, file),
  to print numerous ASCII constant strings among mysql.cc
  code, to avoid strlen(s) in my_win_console_fputs().
*/
void tee_fputs(const char *s, FILE *file) {
#ifdef _WIN32
  if (my_win_is_console_cached(file))
    my_win_console_fputs(charset_info, s);
  else
#endif
    fputs(s, file);
  if (opt_outfile) fputs(s, OUTFILE);
}

void tee_puts(const char *s, FILE *file) {
  tee_fputs(s, file);
  tee_putc('\n', file);
}

void tee_putc(int c, FILE *file) {
#ifdef _WIN32
  if (my_win_is_console_cached(file))
    my_win_console_putc(charset_info, c);
  else
#endif
    putc(c, file);
  if (opt_outfile) putc(c, OUTFILE);
}

#if defined(_WIN32)
#include <time.h>
#else
#include <sys/times.h>
#ifdef _SC_CLK_TCK  // For mit-pthreads
#undef CLOCKS_PER_SEC
#define CLOCKS_PER_SEC (sysconf(_SC_CLK_TCK))
#endif
#endif

/**
  Write as many as 52+1 bytes to buff, in the form of a legible duration of
  time.

  len("4294967296 days, 23 hours, 59 minutes, 60.00 seconds")  ->  52
*/
static void nice_time(double sec, char *buff, bool part_second) {
  ulong tmp;
  if (sec >= 3600.0 * 24) {
    tmp = (ulong)floor(sec / (3600.0 * 24));
    sec -= 3600.0 * 24 * tmp;
    buff = longlong10_to_str(tmp, buff, 10);
    buff = my_stpcpy(buff, tmp > 1 ? " days " : " day ");
  }
  if (sec >= 3600.0) {
    tmp = (ulong)floor(sec / 3600.0);
    sec -= 3600.0 * tmp;
    buff = longlong10_to_str(tmp, buff, 10);
    buff = my_stpcpy(buff, tmp > 1 ? " hours " : " hour ");
  }
  if (sec >= 60.0) {
    tmp = (ulong)floor(sec / 60.0);
    sec -= 60.0 * tmp;
    buff = longlong10_to_str(tmp, buff, 10);
    buff = my_stpcpy(buff, " min ");
  }
  if (part_second)
    sprintf(buff, "%.3f sec", sec);
  else
    sprintf(buff, "%d sec", (int)sec);
}

static void end_timer(ulonglong start_time, char *buff) {
  ulonglong end_time = my_timer_microseconds();
  nice_time((double)(end_time > start_time ? end_time - start_time : 0) /
                (double)(1000000),
            buff, true);
}

static void mysql_end_timer(ulonglong start_time, char *buff) {
  buff[0] = ' ';
  buff[1] = '(';
  end_timer(start_time, buff + 2);
  my_stpcpy(strend(buff), ")");
}

static const char *construct_prompt() {
  processed_prompt.mem_free();          // Erase the old prompt
  const time_t lclock = time(nullptr);  // Get the date struct
  struct tm *t = localtime(&lclock);

  /* parse thru the settings for the prompt */
  for (char *c = current_prompt; *c; c++) {
    if (*c != PROMPT_CHAR)
      processed_prompt.append(*c);
    else {
      switch (*++c) {
        case '\0':
          c--;  // stop it from going beyond if ends with %
          break;
        case 'c':
          add_int_to_prompt(++prompt_counter);
          break;
        case 'C':
          add_int_to_prompt(mysql_thread_id(&mysql_handle));
          break;
        case 'v':
          if (connected)
            processed_prompt.append(mysql_get_server_info(&mysql_handle));
          else
            processed_prompt.append("not_connected");
          break;
        case 'd':
          processed_prompt.append(current_db ? current_db : "(none)");
          break;
        case 'h': {
          const char *prompt;
          prompt =
              connected ? mysql_get_host_info(&mysql_handle) : "not_connected";
          if (strstr(prompt, "Localhost"))
            processed_prompt.append("localhost");
          else {
            const char *end = strcend(prompt, ' ');
            processed_prompt.append(prompt, (uint)(end - prompt));
          }
          break;
        }
        case 'p': {
          if (!connected) {
            processed_prompt.append("not_connected");
            break;
          }

          const char *host_info = mysql_get_host_info(&mysql_handle);
          if (strstr(host_info, "memory")) {
            processed_prompt.append(mysql_handle.host);
          } else if (strstr(host_info, "TCP/IP") || !mysql_handle.unix_socket)
            add_int_to_prompt(mysql_handle.port);
          else {
            char *pos = strrchr(mysql_handle.unix_socket, '/');
            processed_prompt.append(pos ? pos + 1 : mysql_handle.unix_socket);
          }
        } break;
        case 'U':
          if (!full_username) init_username();
          processed_prompt.append(
              full_username ? full_username
                            : (current_user ? current_user : "(unknown)"));
          break;
        case 'u':
          if (!full_username) init_username();
          processed_prompt.append(
              part_username ? part_username
                            : (current_user ? current_user : "(unknown)"));
          break;
        case PROMPT_CHAR:
          processed_prompt.append(PROMPT_CHAR);
          break;
        case 'n':
          processed_prompt.append('\n');
          break;
        case ' ':
        case '_':
          processed_prompt.append(' ');
          break;
        case 'R':
          if (t->tm_hour < 10) processed_prompt.append('0');
          add_int_to_prompt(t->tm_hour);
          break;
        case 'r':
          int getHour;
          getHour = t->tm_hour % 12;
          if (getHour == 0) getHour = 12;
          if (getHour < 10) processed_prompt.append('0');
          add_int_to_prompt(getHour);
          break;
        case 'm':
          if (t->tm_min < 10) processed_prompt.append('0');
          add_int_to_prompt(t->tm_min);
          break;
        case 'y':
          int getYear;
          getYear = t->tm_year % 100;
          if (getYear < 10) processed_prompt.append('0');
          add_int_to_prompt(getYear);
          break;
        case 'Y':
          add_int_to_prompt(t->tm_year + 1900);
          break;
        case 'D':
          char *dateTime;
          dateTime = ctime(&lclock);
          processed_prompt.append(strtok(dateTime, "\n"));
          break;
        case 's':
          if (t->tm_sec < 10) processed_prompt.append('0');
          add_int_to_prompt(t->tm_sec);
          break;
        case 'w':
          processed_prompt.append(day_names[t->tm_wday]);
          break;
        case 'P':
          processed_prompt.append(t->tm_hour < 12 ? "am" : "pm");
          break;
        case 'o':
          add_int_to_prompt(t->tm_mon + 1);
          break;
        case 'O':
          processed_prompt.append(month_names[t->tm_mon]);
          break;
        case '\'':
          processed_prompt.append("'");
          break;
        case '"':
          processed_prompt.append('"');
          break;
        case 'S':
          processed_prompt.append(';');
          break;
        case 't':
          processed_prompt.append('\t');
          break;
        case 'l':
          processed_prompt.append(delimiter_str);
          break;
        case 'T':
          if (mysql_handle.server_status & SERVER_STATUS_IN_TRANS)
            processed_prompt.append("*");
          break;
        default:
          processed_prompt.append(c);
      }
    }
  }
  processed_prompt.append('\0');
  return processed_prompt.ptr();
}

static void add_int_to_prompt(int toadd) {
  processed_prompt.append_longlong(toadd);
}

static void init_username() {
  my_free(full_username);
  my_free(part_username);

  MYSQL_RES *result = nullptr;
  if (!mysql_query(&mysql_handle, "select USER()") &&
      (result = mysql_use_result(&mysql_handle))) {
    MYSQL_ROW cur = mysql_fetch_row(result);
    full_username = my_strdup(PSI_NOT_INSTRUMENTED, cur[0], MYF(MY_WME));
    part_username =
        my_strdup(PSI_NOT_INSTRUMENTED, strtok(cur[0], "@"), MYF(MY_WME));
    (void)mysql_fetch_row(result);  // Read eof
  }
}

// Get the current OS user name.
static void get_current_os_user() {
  const char *user;

#ifdef _WIN32
  char buf[255];
  WCHAR wbuf[255];
  DWORD wbuf_len = sizeof(wbuf) / sizeof(WCHAR);
  size_t len;
  uint dummy_errors;

  if (GetUserNameW(wbuf, &wbuf_len)) {
    len = my_convert(buf, sizeof(buf) - 1, charset_info, (char *)wbuf,
                     wbuf_len * sizeof(WCHAR),
                     get_charset_by_name("utf16le_bin", MYF(0)), &dummy_errors);
    buf[len] = 0;
    user = buf;
  } else {
    user = "UNKNOWN USER";
  }
#else
#ifdef HAVE_GETPWUID
  struct passwd *pw = getpwuid(geteuid());

  if (pw != nullptr)
    user = pw->pw_name;
  else
#endif
      if (!(user = getenv("USER")) && !(user = getenv("LOGNAME")) &&
          !(user = getenv("LOGIN")))
    user = "UNKNOWN USER";
#endif /* _WIN32 */
  current_os_user = my_strdup(PSI_NOT_INSTRUMENTED, user, MYF(MY_WME));
}

// Get the current OS sudo user name (only for non-Windows platforms).
static void get_current_os_sudouser() {
#ifndef _WIN32
  if (getenv("SUDO_USER"))
    current_os_sudouser =
        my_strdup(PSI_NOT_INSTRUMENTED, getenv("SUDO_USER"), MYF(MY_WME));
#endif /* !_WIN32 */
}

static int com_prompt(String *buffer [[maybe_unused]], char *line) {
  char *ptr = strchr(line, ' ');
  prompt_counter = 0;
  my_free(current_prompt);
  current_prompt = my_strdup(PSI_NOT_INSTRUMENTED,
                             ptr ? ptr + 1 : default_prompt, MYF(MY_WME));
  if (!ptr)
    tee_fprintf(stdout, "Returning to default PROMPT of %s\n", default_prompt);
  else
    tee_fprintf(stdout, "PROMPT set to '%s'\n", current_prompt);
  return 0;
}

static int com_resetconnection(String *buffer [[maybe_unused]],
                               char *line [[maybe_unused]]) {
  int error;
  global_attrs->clear(connected ? &mysql_handle : nullptr);
  error = mysql_reset_connection(&mysql_handle);
  if (error) {
    if (status.batch) return 0;
    return put_error(&mysql_handle);
  }
  return error;
}
