From 3f619478f796eddbba6e39502fe941b285dd97b1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 20:00:34 +0200 Subject: Adding upstream version 1:10.11.6. Signed-off-by: Daniel Baumann --- client/CMakeLists.txt | 103 + client/async_example.c | 216 + client/client_metadata.h | 57 + client/client_priv.h | 155 + client/completion_hash.cc | 226 + client/completion_hash.h | 61 + client/echo.c | 45 + client/mariadb-conv.cc | 484 ++ client/my_readline.h | 42 + client/mysql.cc | 5610 ++++++++++++++++++++ client/mysql_plugin.c | 1244 +++++ client/mysql_upgrade.c | 1524 ++++++ client/mysqladmin.cc | 1712 +++++++ client/mysqlbinlog.cc | 3802 ++++++++++++++ client/mysqlcheck.c | 1298 +++++ client/mysqldump.c | 7292 ++++++++++++++++++++++++++ client/mysqlimport.c | 788 +++ client/mysqlshow.c | 964 ++++ client/mysqlslap.c | 2331 +++++++++ client/mysqltest.cc | 12109 ++++++++++++++++++++++++++++++++++++++++++++ client/readline.cc | 262 + 21 files changed, 40325 insertions(+) create mode 100644 client/CMakeLists.txt create mode 100644 client/async_example.c create mode 100644 client/client_metadata.h create mode 100644 client/client_priv.h create mode 100644 client/completion_hash.cc create mode 100644 client/completion_hash.h create mode 100644 client/echo.c create mode 100644 client/mariadb-conv.cc create mode 100644 client/my_readline.h create mode 100644 client/mysql.cc create mode 100644 client/mysql_plugin.c create mode 100644 client/mysql_upgrade.c create mode 100644 client/mysqladmin.cc create mode 100644 client/mysqlbinlog.cc create mode 100644 client/mysqlcheck.c create mode 100644 client/mysqldump.c create mode 100644 client/mysqlimport.c create mode 100644 client/mysqlshow.c create mode 100644 client/mysqlslap.c create mode 100644 client/mysqltest.cc create mode 100644 client/readline.cc (limited to 'client') diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt new file mode 100644 index 00000000..55fd02b2 --- /dev/null +++ b/client/CMakeLists.txt @@ -0,0 +1,103 @@ +# Copyright (c) 2006, 2015, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2008, 2019, MariaDB Corporation +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 of the License. +# +# 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 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-1335 USA + +INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/include + ${PCRE_INCLUDES} + ${CMAKE_SOURCE_DIR}/mysys_ssl + ${ZLIB_INCLUDE_DIR} + ${SSL_INCLUDE_DIRS} + ${CMAKE_SOURCE_DIR}/sql + ${CMAKE_SOURCE_DIR}/strings + ${MY_READLINE_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} +) + +INCLUDE_DIRECTORIES(BEFORE + ${CMAKE_BINARY_DIR}/libmariadb/include + ${CMAKE_SOURCE_DIR}/libmariadb/include) + +## We will need libeay32.dll and ssleay32.dll when running client executables. +COPY_OPENSSL_DLLS(copy_openssl_client) + +SET(CLIENT_LIB mariadbclient mysys) + +ADD_DEFINITIONS(${SSL_DEFINES}) +MYSQL_ADD_EXECUTABLE(mariadb completion_hash.cc mysql.cc readline.cc + ${CMAKE_SOURCE_DIR}/sql/sql_string.cc) +TARGET_LINK_LIBRARIES(mariadb ${CLIENT_LIB}) +IF(UNIX) + TARGET_LINK_LIBRARIES(mariadb ${MY_READLINE_LIBRARY}) + SET_TARGET_PROPERTIES(mariadb PROPERTIES ENABLE_EXPORTS TRUE) +ENDIF(UNIX) + +MYSQL_ADD_EXECUTABLE(mariadb-test mysqltest.cc ${CMAKE_SOURCE_DIR}/sql/sql_string.cc COMPONENT Test) +SET_SOURCE_FILES_PROPERTIES(mysqltest.cc PROPERTIES COMPILE_FLAGS "-DTHREADS ${PCRE2_DEBIAN_HACK}") +TARGET_LINK_LIBRARIES(mariadb-test ${CLIENT_LIB} pcre2-posix pcre2-8) +SET_TARGET_PROPERTIES(mariadb-test PROPERTIES ENABLE_EXPORTS TRUE) + + +MYSQL_ADD_EXECUTABLE(mariadb-check mysqlcheck.c) +TARGET_LINK_LIBRARIES(mariadb-check ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-dump mysqldump.c ../sql-common/my_user.c) +TARGET_LINK_LIBRARIES(mariadb-dump ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-import mysqlimport.c) +SET_SOURCE_FILES_PROPERTIES(mysqlimport.c PROPERTIES COMPILE_FLAGS "-DTHREADS") +TARGET_LINK_LIBRARIES(mariadb-import ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-upgrade mysql_upgrade.c COMPONENT Server) +TARGET_LINK_LIBRARIES(mariadb-upgrade ${CLIENT_LIB}) +ADD_DEPENDENCIES(mariadb-upgrade GenFixPrivs) + +MYSQL_ADD_EXECUTABLE(mariadb-show mysqlshow.c) +TARGET_LINK_LIBRARIES(mariadb-show ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-plugin mysql_plugin.c) +TARGET_LINK_LIBRARIES(mariadb-plugin ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-binlog mysqlbinlog.cc) +TARGET_LINK_LIBRARIES(mariadb-binlog ${CLIENT_LIB} mysys_ssl) + +MYSQL_ADD_EXECUTABLE(mariadb-admin mysqladmin.cc ../sql/password.c) +TARGET_LINK_LIBRARIES(mariadb-admin ${CLIENT_LIB} mysys_ssl) + +MYSQL_ADD_EXECUTABLE(mariadb-slap mysqlslap.c) +SET_SOURCE_FILES_PROPERTIES(mysqlslap.c PROPERTIES COMPILE_FLAGS "-DTHREADS") +TARGET_LINK_LIBRARIES(mariadb-slap ${CLIENT_LIB}) + +MYSQL_ADD_EXECUTABLE(mariadb-conv mariadb-conv.cc + ${CMAKE_SOURCE_DIR}/sql/sql_string.cc) +TARGET_LINK_LIBRARIES(mariadb-conv mysys strings) + +IF(WIN32) + MYSQL_ADD_EXECUTABLE(echo echo.c COMPONENT Test) +ENDIF(WIN32) + +# async_example is just a code example, do not install it. +ADD_EXECUTABLE(async_example async_example.c) +TARGET_LINK_LIBRARIES(async_example ${CLIENT_LIB}) + +SET_TARGET_PROPERTIES (mariadb-check mariadb-dump mariadb-import mariadb-upgrade mariadb-show mariadb-slap mariadb-plugin async_example +PROPERTIES HAS_CXX TRUE) + +FOREACH(t mariadb mariadb-test mariadb-check mariadb-dump mariadb-import mariadb-upgrade mariadb-show mariadb-plugin mariadb-binlog + mariadb-admin mariadb-slap async_example) + ADD_DEPENDENCIES(${t} GenError ${CLIENT_LIB}) +ENDFOREACH() + +ADD_DEFINITIONS(-DHAVE_DLOPEN) diff --git a/client/async_example.c b/client/async_example.c new file mode 100644 index 00000000..f216de22 --- /dev/null +++ b/client/async_example.c @@ -0,0 +1,216 @@ +/* + Copyright 2011 Kristian Nielsen and Monty Program Ab. + + This file is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this. If not, see . +*/ + + +#ifndef _WIN32 +#include +#else +#include +#endif + +#include +#include +#include + +#define SL(s) (s), sizeof(s) + +static const char *my_groups[]= { "client", NULL }; + +static int +wait_for_mysql(MYSQL *mysql, int status) +{ +#ifdef _WIN32 + fd_set rs, ws, es; + int res; + struct timeval tv, *timeout; + my_socket s= mysql_get_socket(mysql); + FD_ZERO(&rs); + FD_ZERO(&ws); + FD_ZERO(&es); + if (status & MYSQL_WAIT_READ) + FD_SET(s, &rs); + if (status & MYSQL_WAIT_WRITE) + FD_SET(s, &ws); + if (status & MYSQL_WAIT_EXCEPT) + FD_SET(s, &es); + if (status & MYSQL_WAIT_TIMEOUT) + { + tv.tv_sec= mysql_get_timeout_value(mysql); + tv.tv_usec= 0; + timeout= &tv; + } + else + timeout= NULL; + res= select(1, &rs, &ws, &es, timeout); + if (res == 0) + return MYSQL_WAIT_TIMEOUT; + else if (res == SOCKET_ERROR) + { + /* + In a real event framework, we should handle errors and re-try the select. + */ + return MYSQL_WAIT_TIMEOUT; + } + else + { + int status= 0; + if (FD_ISSET(s, &rs)) + status|= MYSQL_WAIT_READ; + if (FD_ISSET(s, &ws)) + status|= MYSQL_WAIT_WRITE; + if (FD_ISSET(s, &es)) + status|= MYSQL_WAIT_EXCEPT; + return status; + } +#else + struct pollfd pfd; + int timeout; + int res; + + pfd.fd= mysql_get_socket(mysql); + pfd.events= + (status & MYSQL_WAIT_READ ? POLLIN : 0) | + (status & MYSQL_WAIT_WRITE ? POLLOUT : 0) | + (status & MYSQL_WAIT_EXCEPT ? POLLPRI : 0); + if (status & MYSQL_WAIT_TIMEOUT) + timeout= 1000*mysql_get_timeout_value(mysql); + else + timeout= -1; + res= poll(&pfd, 1, timeout); + if (res == 0) + return MYSQL_WAIT_TIMEOUT; + else if (res < 0) + { + /* + In a real event framework, we should handle EINTR and re-try the poll. + */ + return MYSQL_WAIT_TIMEOUT; + } + else + { + int status= 0; + if (pfd.revents & POLLIN) + status|= MYSQL_WAIT_READ; + if (pfd.revents & POLLOUT) + status|= MYSQL_WAIT_WRITE; + if (pfd.revents & POLLPRI) + status|= MYSQL_WAIT_EXCEPT; + return status; + } +#endif +} + +static void +fatal(MYSQL *mysql, const char *msg) +{ + fprintf(stderr, "%s: %s\n", msg, mysql_error(mysql)); + exit(1); +} + +static void +doit(const char *host, const char *user, const char *password) +{ + int err; + MYSQL mysql, *ret; + MYSQL_RES *res; + MYSQL_ROW row; + int status; + + mysql_init(&mysql); + mysql_options(&mysql, MYSQL_OPT_NONBLOCK, 0); + mysql_options(&mysql, MYSQL_READ_DEFAULT_GROUP, "myapp"); + + /* Returns 0 when done, else flag for what to wait for when need to block. */ + status= mysql_real_connect_start(&ret, &mysql, host, user, password, NULL, + 0, NULL, 0); + while (status) + { + status= wait_for_mysql(&mysql, status); + status= mysql_real_connect_cont(&ret, &mysql, status); + } + + if (!ret) + fatal(&mysql, "Failed to mysql_real_connect()"); + + status= mysql_real_query_start(&err, &mysql, SL("SHOW STATUS")); + while (status) + { + status= wait_for_mysql(&mysql, status); + status= mysql_real_query_cont(&err, &mysql, status); + } + if (err) + fatal(&mysql, "mysql_real_query() returns error"); + + /* This method cannot block. */ + res= mysql_use_result(&mysql); + if (!res) + fatal(&mysql, "mysql_use_result() returns error"); + + for (;;) + { + status= mysql_fetch_row_start(&row, res); + while (status) + { + status= wait_for_mysql(&mysql, status); + status= mysql_fetch_row_cont(&row, res, status); + } + if (!row) + break; + printf("%s: %s\n", row[0], row[1]); + } + if (mysql_errno(&mysql)) + fatal(&mysql, "Got error while retrieving rows"); + mysql_free_result(res); + + /* + mysql_close() sends a COM_QUIT packet, and so in principle could block + waiting for the socket to accept the data. + In practise, for many applications it will probably be fine to use the + blocking mysql_close(). + */ + status= mysql_close_start(&mysql); + while (status) + { + status= wait_for_mysql(&mysql, status); + status= mysql_close_cont(&mysql, status); + } +} + +int +main(int argc, char *argv[]) +{ + int err; + + if (argc != 4) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + err= mysql_library_init(argc, argv, (char **)my_groups); + if (err) + { + fprintf(stderr, "Fatal: mysql_library_init() returns error: %d\n", err); + exit(1); + } + + doit(argv[1], argv[2], argv[3]); + + mysql_library_end(); + + return 0; +} diff --git a/client/client_metadata.h b/client/client_metadata.h new file mode 100644 index 00000000..49921f01 --- /dev/null +++ b/client/client_metadata.h @@ -0,0 +1,57 @@ +#ifndef SQL_CLIENT_METADATA_INCLUDED +#define SQL_CLIENT_METADATA_INCLUDED +/* + Copyright (c) 2020, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA */ + +#include "sql_string.h" + + +/* + Print MYSQL_FIELD metadata in human readable format +*/ +class Client_field_metadata +{ + const MYSQL_FIELD *m_field; +public: + Client_field_metadata(MYSQL_FIELD *field) + :m_field(field) + { } + void print_attr(Binary_string *to, + const LEX_CSTRING &name, + mariadb_field_attr_t attr, + uint orig_to_length) const + { + MARIADB_CONST_STRING tmp; + if (!mariadb_field_attr(&tmp, m_field, attr) && tmp.length) + { + if (to->length() != orig_to_length) + to->append(" ", 1); + to->append(name); + to->append(tmp.str, tmp.length); + } + } + void print_data_type_related_attributes(Binary_string *to) const + { + static const LEX_CSTRING type= {C_STRING_WITH_LEN("type=")}; + static const LEX_CSTRING format= {C_STRING_WITH_LEN("format=")}; + uint to_length_orig= to->length(); + print_attr(to, type, MARIADB_FIELD_ATTR_DATA_TYPE_NAME, to_length_orig); + print_attr(to, format, MARIADB_FIELD_ATTR_FORMAT_NAME, to_length_orig); + } +}; + + +#endif // SQL_CLIENT_METADATA_INCLUDED diff --git a/client/client_priv.h b/client/client_priv.h new file mode 100644 index 00000000..597c074c --- /dev/null +++ b/client/client_priv.h @@ -0,0 +1,155 @@ +/* + Copyright (c) 2001, 2012, Oracle and/or its affiliates. + Copyright (c) 2009, 2022, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* Common defines for all clients */ + +#include +#include +#include +#include +#include +#include +#include + +#ifndef WEXITSTATUS +# ifdef _WIN32 +# define WEXITSTATUS(stat_val) (stat_val) +# else +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +# endif +#endif + +enum options_client +{ + OPT_CHARSETS_DIR=256, OPT_DEFAULT_CHARSET, + OPT_PAGER, OPT_TEE, + OPT_LOW_PRIORITY, OPT_AUTO_REPAIR, OPT_COMPRESS, + OPT_DROP, OPT_LOCKS, OPT_KEYWORDS, OPT_DELAYED, OPT_OPTIMIZE, + OPT_FTB, OPT_LTB, OPT_ENC, OPT_O_ENC, OPT_ESC, OPT_TABLES, + OPT_MASTER_DATA, OPT_AUTOCOMMIT, OPT_AUTO_REHASH, + OPT_LINE_NUMBERS, OPT_COLUMN_NAMES, OPT_CONNECT_TIMEOUT, + OPT_MAX_ALLOWED_PACKET, OPT_NET_BUFFER_LENGTH, + OPT_SELECT_LIMIT, OPT_MAX_JOIN_SIZE, OPT_SSL_SSL, + OPT_SSL_KEY, OPT_SSL_CERT, OPT_SSL_CA, OPT_SSL_CAPATH, + OPT_SSL_CIPHER, OPT_TLS_VERSION, OPT_SHUTDOWN_TIMEOUT, OPT_LOCAL_INFILE, + OPT_DELETE_MASTER_LOGS, OPT_COMPACT, + OPT_PROMPT, OPT_IGN_LINES,OPT_TRANSACTION,OPT_MYSQL_PROTOCOL, + OPT_FRM, OPT_SKIP_OPTIMIZATION, + OPT_COMPATIBLE, OPT_RECONNECT, OPT_DELIMITER, OPT_SECURE_AUTH, + OPT_OPEN_FILES_LIMIT, OPT_SET_CHARSET, OPT_SERVER_ARG, + OPT_STOP_POSITION, OPT_START_DATETIME, OPT_STOP_DATETIME, + OPT_SIGINT_IGNORE, OPT_HEXBLOB, OPT_ORDER_BY_PRIMARY, OPT_COUNT, + OPT_FLUSH_TABLES, + OPT_TRIGGERS, + OPT_MYSQL_ONLY_PRINT, + OPT_MYSQL_LOCK_DIRECTORY, + OPT_USE_THREADS, + OPT_IMPORT_USE_THREADS, + OPT_MYSQL_NUMBER_OF_QUERY, + OPT_IGNORE_DATABASE, + OPT_IGNORE_TABLE,OPT_INSERT_IGNORE,OPT_SHOW_WARNINGS,OPT_DROP_DATABASE, + OPT_TZ_UTC, OPT_CREATE_SLAP_SCHEMA, + OPT_MYSQLDUMP_SLAVE_APPLY, + OPT_MYSQLDUMP_SLAVE_DATA, + OPT_MYSQLDUMP_INCLUDE_MASTER_HOST_PORT, +#ifdef WHEN_FLASHBACK_REVIEW_READY + OPT_REVIEW, + OPT_REVIEW_DBNAME, OPT_REVIEW_TABLENAME, +#endif + OPT_SLAP_CSV, OPT_SLAP_CREATE_STRING, + OPT_SLAP_AUTO_GENERATE_SQL_LOAD_TYPE, OPT_SLAP_AUTO_GENERATE_WRITE_NUM, + OPT_SLAP_AUTO_GENERATE_ADD_AUTO, + OPT_SLAP_AUTO_GENERATE_GUID_PRIMARY, + OPT_SLAP_AUTO_GENERATE_EXECUTE_QUERIES, + OPT_SLAP_AUTO_GENERATE_SECONDARY_INDEXES, + OPT_SLAP_AUTO_GENERATE_UNIQUE_WRITE_NUM, + OPT_SLAP_AUTO_GENERATE_UNIQUE_QUERY_NUM, + OPT_SLAP_PRE_QUERY, + OPT_SLAP_POST_QUERY, + OPT_SLAP_PRE_SYSTEM, + OPT_SLAP_POST_SYSTEM, + OPT_SLAP_COMMIT, + OPT_SLAP_DETACH, + OPT_SLAP_NO_DROP, + OPT_MYSQL_REPLACE_INTO, OPT_BASE64_OUTPUT_MODE, OPT_SERVER_ID, + OPT_FIX_TABLE_NAMES, OPT_FIX_DB_NAMES, OPT_SSL_VERIFY_SERVER_CERT, + OPT_AUTO_VERTICAL_OUTPUT, + OPT_DEBUG_INFO, OPT_DEBUG_CHECK, OPT_COLUMN_TYPES, OPT_ERROR_LOG_FILE, + OPT_WRITE_BINLOG, OPT_DUMP_DATE, + OPT_INIT_COMMAND, + OPT_PLUGIN_DIR, + OPT_DEFAULT_AUTH, + OPT_ABORT_SOURCE_ON_ERROR, + OPT_REWRITE_DB, + OPT_REPORT_PROGRESS, + OPT_SKIP_ANNOTATE_ROWS_EVENTS, + OPT_SSL_CRL, OPT_SSL_CRLPATH, + OPT_IGNORE_DATA, + OPT_PRINT_ROW_COUNT, OPT_PRINT_ROW_EVENT_POSITIONS, + OPT_CHECK_IF_UPGRADE_NEEDED, + OPT_COMPATIBILTY_CLEARTEXT_PLUGIN, + OPT_SHUTDOWN_WAIT_FOR_SLAVES, + OPT_COPY_S3_TABLES, + OPT_PRINT_TABLE_METADATA, + OPT_ASOF_TIMESTAMP, + OPT_IGNORE_DOMAIN_IDS, + OPT_DO_DOMAIN_IDS, + OPT_IGNORE_SERVER_IDS, + OPT_DO_SERVER_IDS, + OPT_MAX_CLIENT_OPTION /* should be always the last */ +}; + +/** + First mysql version supporting the information schema. +*/ +#define FIRST_INFORMATION_SCHEMA_VERSION 50003 + +/** + Name of the information schema database. +*/ +#define INFORMATION_SCHEMA_DB_NAME "information_schema" + +/** + First mysql version supporting the performance schema. +*/ +#define FIRST_PERFORMANCE_SCHEMA_VERSION 50503 + +/** + Name of the performance schema database. +*/ +#define PERFORMANCE_SCHEMA_DB_NAME "performance_schema" + +/** + First mariadb version supporting the sys schema. +*/ +#define FIRST_SYS_SCHEMA_VERSION 100600 + +/** + Name of the sys schema database. +*/ +#define SYS_SCHEMA_DB_NAME "sys" + +/** + The --socket CLI option has different meanings + across different operating systems. + */ +#ifndef _WIN32 +#define SOCKET_PROTOCOL_TO_FORCE MYSQL_PROTOCOL_SOCKET +#else +#define SOCKET_PROTOCOL_TO_FORCE MYSQL_PROTOCOL_PIPE +#endif diff --git a/client/completion_hash.cc b/client/completion_hash.cc new file mode 100644 index 00000000..0a13b790 --- /dev/null +++ b/client/completion_hash.cc @@ -0,0 +1,226 @@ +/* Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA */ + +/* Quick & light hash implementation for tab completion purposes + * + * by Andi Gutmans + * and Zeev Suraski + * Small portability changes by Monty. Changed also to use my_malloc/my_free + */ + +#include +#include +#include +#include "completion_hash.h" + +uint hashpjw(const char *arKey, uint nKeyLength) +{ + uint h = 0, g, i; + + for (i = 0; i < nKeyLength; i++) { + h = (h << 4) + arKey[i]; + if ((g = (h & 0xF0000000))) { + h = h ^ (g >> 24); + h = h ^ g; + } + } + return h; +} + +int completion_hash_init(HashTable *ht, uint nSize) +{ + ht->arBuckets = (Bucket **) my_malloc(PSI_NOT_INSTRUMENTED, + nSize* sizeof(Bucket *), MYF(MY_ZEROFILL | MY_WME)); + + if (!ht->arBuckets) + { + ht->initialized = 0; + return FAILURE; + } + init_alloc_root(PSI_NOT_INSTRUMENTED, &ht->mem_root, 8192, 0, MYF(0)); + ht->pHashFunction = hashpjw; + ht->nTableSize = nSize; + ht->initialized = 1; + return SUCCESS; +} + + +int completion_hash_update(HashTable *ht, char *arKey, uint nKeyLength, + char *str) +{ + uint h, nIndex; + + Bucket *p; + + h = ht->pHashFunction(arKey, nKeyLength); + nIndex = h % ht->nTableSize; + + if (nKeyLength <= 0) { + return FAILURE; + } + p = ht->arBuckets[nIndex]; + while (p) + { + if ((p->h == h) && (p->nKeyLength == nKeyLength)) { + if (!memcmp(p->arKey, arKey, nKeyLength)) { + entry *n; + + if (!(n = (entry *) alloc_root(&ht->mem_root,sizeof(entry)))) + return FAILURE; + n->pNext = p->pData; + n->str = str; + p->pData = n; + p->count++; + + return SUCCESS; + } + } + p = p->pNext; + } + + if (!(p = (Bucket *) alloc_root(&ht->mem_root, sizeof(Bucket)))) + return FAILURE; + + p->arKey = arKey; + p->nKeyLength = nKeyLength; + p->h = h; + + if (!(p->pData = (entry*) alloc_root(&ht->mem_root, sizeof(entry)))) + return FAILURE; + + p->pData->str = str; + p->pData->pNext = 0; + p->count = 1; + + p->pNext = ht->arBuckets[nIndex]; + ht->arBuckets[nIndex] = p; + + return SUCCESS; +} + +static Bucket *completion_hash_find(HashTable *ht, const char *arKey, + uint nKeyLength) +{ + uint h, nIndex; + Bucket *p; + + h = ht->pHashFunction(arKey, nKeyLength); + nIndex = h % ht->nTableSize; + + p = ht->arBuckets[nIndex]; + while (p) + { + if ((p->h == h) && (p->nKeyLength == nKeyLength)) { + if (!memcmp(p->arKey, arKey, nKeyLength)) { + return p; + } + } + p = p->pNext; + } + return (Bucket*) 0; +} + + +int completion_hash_exists(HashTable *ht, char *arKey, uint nKeyLength) +{ + uint h, nIndex; + Bucket *p; + + h = ht->pHashFunction(arKey, nKeyLength); + nIndex = h % ht->nTableSize; + + p = ht->arBuckets[nIndex]; + while (p) + { + if ((p->h == h) && (p->nKeyLength == nKeyLength)) + { + if (!strcmp(p->arKey, arKey)) { + return 1; + } + } + p = p->pNext; + } + return 0; +} + +Bucket *find_all_matches(HashTable *ht, const char *str, uint length, + uint *res_length) +{ + Bucket *b; + + b = completion_hash_find(ht,str,length); + if (!b) { + *res_length = 0; + return (Bucket*) 0; + } else { + *res_length = length; + return b; + } +} + +Bucket *find_longest_match(HashTable *ht, char *str, uint length, + uint *res_length) +{ + Bucket *b,*return_b; + char *s; + uint count; + uint lm; + + b = completion_hash_find(ht,str,length); + if (!b) { + *res_length = 0; + return (Bucket*) 0; + } + + count = b->count; + lm = length; + s = b->pData->str; + + return_b = b; + while (s[lm]!=0 && (b=completion_hash_find(ht,s,lm+1))) { + if (b->countmem_root,MYF(0)); + if (size_t s= ht->nTableSize) + bzero((char*) ht->arBuckets, s * sizeof(Bucket *)); +} + + +void completion_hash_free(HashTable *ht) +{ + completion_hash_clean(ht); + my_free(ht->arBuckets); +} + + +void add_word(HashTable *ht,char *str) +{ + int i; + char *pos=str; + for (i=1; *pos; i++, pos++) + completion_hash_update(ht, str, i, str); +} diff --git a/client/completion_hash.h b/client/completion_hash.h new file mode 100644 index 00000000..57483e0f --- /dev/null +++ b/client/completion_hash.h @@ -0,0 +1,61 @@ +/* Copyright (c) 2000-2002, 2006 MySQL AB + Use is subject to license terms + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; version 2 + of the License. + + 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + MA 02110-1335 USA */ + +#ifndef _HASH_ +#define _HASH_ + +#define SUCCESS 0 +#define FAILURE 1 + +#include +#include + +typedef struct _entry { + char *str; + struct _entry *pNext; +} entry; + +typedef struct bucket +{ + uint h; /* Used for numeric indexing */ + char *arKey; + uint nKeyLength; + uint count; + entry *pData; + struct bucket *pNext; +} Bucket; + +typedef struct hashtable { + uint nTableSize; + uint initialized; + MEM_ROOT mem_root; + uint(*pHashFunction) (const char *arKey, uint nKeyLength); + Bucket **arBuckets; +} HashTable; + +extern int completion_hash_init(HashTable *ht, uint nSize); +extern int completion_hash_update(HashTable *ht, char *arKey, uint nKeyLength, char *str); +extern int hash_exists(HashTable *ht, char *arKey); +extern Bucket *find_all_matches(HashTable *ht, const char *str, uint length, uint *res_length); +extern Bucket *find_longest_match(HashTable *ht, char *str, uint length, uint *res_length); +extern void add_word(HashTable *ht,char *str); +extern void completion_hash_clean(HashTable *ht); +extern int completion_hash_exists(HashTable *ht, char *arKey, uint nKeyLength); +extern void completion_hash_free(HashTable *ht); + +#endif /* _HASH_ */ diff --git a/client/echo.c b/client/echo.c new file mode 100644 index 00000000..6904f541 --- /dev/null +++ b/client/echo.c @@ -0,0 +1,45 @@ +/* Copyright (c) 2000, 2007 MySQL AB + Use is subject to license terms + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* + echo is a replacement for the "echo" command builtin to cmd.exe + on Windows, to get a Unix equivalent behaviour when running commands + like: + $> echo "hello" | mysql + + The windows "echo" would have sent "hello" to mysql while + Unix echo will send hello without the enclosing hyphens + + This is a very advanced high tech program so take care when + you change it and remember to valgrind it before production + use. + +*/ + +#include + +int main(int argc, char **argv) +{ + int i; + for (i= 1; i < argc; i++) + { + fprintf(stdout, "%s", argv[i]); + if (i < argc - 1) + fprintf(stdout, " "); + } + fprintf(stdout, "\n"); + return 0; +} diff --git a/client/mariadb-conv.cc b/client/mariadb-conv.cc new file mode 100644 index 00000000..1774debe --- /dev/null +++ b/client/mariadb-conv.cc @@ -0,0 +1,484 @@ +/* + Copyright (c) 2001, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2019, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 +*/ + +/* + Character set conversion utility +*/ + +#include "mariadb.h" +#include "client_priv.h" +#include "sql_string.h" +#include "my_dir.h" + +#define CONV_VERSION "1.0" + + +class CmdOpt +{ +public: + const char *m_charset_from; + const char *m_charset_to; + const char *m_delimiter; + my_bool m_continue; + CmdOpt() + :m_charset_from("latin1"), + m_charset_to("latin1"), + m_delimiter(NULL), + m_continue(FALSE) + { } + static CHARSET_INFO *csinfo_by_name(const char *csname) + { + return get_charset_by_csname(csname, MY_CS_PRIMARY, MYF(MY_UTF8_IS_UTF8MB3)); + } + CHARSET_INFO *csinfo_from() const + { + return m_charset_from ? csinfo_by_name(m_charset_from) : NULL; + } + CHARSET_INFO *csinfo_to() const + { + return m_charset_to ? csinfo_by_name(m_charset_to) : NULL; + } +}; + + +static CmdOpt opt; + + +static struct my_option long_options[] = +{ + {"from", 'f', "Specifies the encoding of the input.", &opt.m_charset_from, + &opt.m_charset_from, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"to", 't', "Specifies the encoding of the output.", &opt.m_charset_to, + &opt.m_charset_to, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"continue", 'c', "Silently ignore conversion errors.", + &opt.m_continue, &opt.m_continue, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"delimiter", 0, "Treat the specified characters as delimiters.", + &opt.m_delimiter, &opt.m_delimiter, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", &charsets_dir, + &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +my_bool +get_one_option(const struct my_option *opt, + const char *value, const char *filename) +{ + return 0; +} + + +class File_buffer: public Binary_string +{ +public: + bool load_binary_stream(FILE *file); + bool load_binary_file_by_name(const char *file); +}; + + +/* + Load data from a binary stream whose length is not known in advance, + e.g. from stdin. +*/ +bool File_buffer::load_binary_stream(FILE *file) +{ + for ( ; ; ) + { + char buf[1024]; + if (length() + sizeof(buf) > UINT_MAX32 || reserve(sizeof(buf))) + { + fprintf(stderr, "Input data is too large\n"); + return true; + } + size_t nbytes= my_fread(file, (uchar *) end(), sizeof(buf), MYF(0)); + if (!nbytes || nbytes == (size_t) -1) + return false; + str_length+= (uint32) nbytes; + } + return false; +} + + +/* + Load data from a file by name. + The file size is know. +*/ +bool File_buffer::load_binary_file_by_name(const char *filename) +{ + MY_STAT sbuf; + File fd; + + if (!my_stat(filename, &sbuf, MYF(0))) + { + fprintf(stderr, "my_stat failed for '%s'\n", filename); + return true; + } + + if (!MY_S_ISREG(sbuf.st_mode)) + { + fprintf(stderr, "'%s' is not a regular file\n", filename); + return true; + } + + if ((size_t) sbuf.st_size > UINT_MAX32) + { + fprintf(stderr, "File '%s' is too large\n", filename); + return true; + } + + if (alloc((uint32) sbuf.st_size)) + { + fprintf(stderr, "Failed to allocate read buffer\n"); + return true; + } + + if ((fd= my_open(filename, O_RDONLY, MYF(0))) == -1) + { + fprintf(stderr, "Could not open '%s'\n", filename); + return true; + } + + size_t nbytes= my_read(fd, (uchar*) Ptr, (size_t)sbuf.st_size, MYF(0)); + my_close(fd, MYF(0)); + length((uint32) nbytes); + + return false; +} + + +class Delimiter +{ +protected: + bool m_delimiter[127]; + bool m_has_delimiter_cached; + bool has_delimiter_slow() const + { + for (size_t i= 0; i < sizeof(m_delimiter); i++) + { + if (m_delimiter[i]) + return true; + } + return false; + } + bool unescape(char *to, char from) const + { + switch (from) { + case '\\': *to= '\\'; return false; + case 'r': *to= '\r'; return false; + case 'n': *to= '\n'; return false; + case 't': *to= '\t'; return false; + case '0': *to= '\0'; return false; + } + *to= '\0'; + return true; + } + bool is_delimiter(char ch) const + { + return (signed char) ch < 0 ? false : m_delimiter[(uint32) ch]; + } +public: + Delimiter() + :m_has_delimiter_cached(false) + { + bzero(&m_delimiter, sizeof(m_delimiter)); + } + bool has_delimiter() const + { + return m_has_delimiter_cached; + } + bool set_delimiter_unescape(const char *str) + { + m_has_delimiter_cached= false; + for ( ; *str; str++) + { + if ((signed char) *str < 0) + return true; + if (*str == '\\') + { + char unescaped; + str++; + if (!*str || unescape(&unescaped, *str)) + return true; + m_delimiter[(uint) unescaped]= true; + } + else + m_delimiter[(uint) *str]= true; + } + m_has_delimiter_cached= has_delimiter_slow(); + return false; + } + size_t get_delimiter_length(const char *str, const char *end) const + { + const char *str0= str; + for ( ; str < end; str++) + { + if (!is_delimiter(*str)) + break; + } + return str - str0; + } + size_t get_data_length(const char *str, const char *end) const + { + const char *str0= str; + for ( ; str < end; str++) + { + if (is_delimiter(*str)) + break; + } + return str - str0; + } +}; + + +class Conv_inbuf +{ + const char *m_ptr; + const char *m_end; +public: + Conv_inbuf(const char *from, size_t length) + :m_ptr(from), m_end(from + length) + { } + const char *ptr() const { return m_ptr; } + const char *end() const { return m_end; } + size_t length() const + { + return m_end - m_ptr; + } +private: + LEX_CSTRING get_prefix(size_t len) + { + LEX_CSTRING res; + res.str= ptr(); + res.length= len; + m_ptr+= len; + return res; + } + LEX_CSTRING get_empty_string() const + { + static LEX_CSTRING str= {NULL, 0}; + return str; + } +public: + LEX_CSTRING get_delimiter_chunk(const Delimiter &delimiter) + { + if (!delimiter.has_delimiter()) + return get_empty_string(); + size_t len= delimiter.get_delimiter_length(ptr(), end()); + return get_prefix(len); + } + LEX_CSTRING get_data_chunk(const Delimiter &delimiter) + { + if (!delimiter.has_delimiter()) + return get_prefix(length()); + size_t len= delimiter.get_data_length(ptr(), end()); + return get_prefix(len); + } +}; + + +class Conv_outbuf: public Binary_string +{ +public: + bool alloc(size_t out_max_length) + { + if (out_max_length >= UINT_MAX32) + { + fprintf(stderr, "The data needs a too large output buffer\n"); + return true; + } + if (Binary_string::alloc((uint32) out_max_length)) + { + fprintf(stderr, "Failed to allocate the output buffer\n"); + return true; + } + return false; + } +}; + + +class Conv: public String_copier, public Delimiter +{ + CHARSET_INFO *m_tocs; + CHARSET_INFO *m_fromcs; + bool m_continue; +public: + Conv(CHARSET_INFO *tocs, CHARSET_INFO *fromcs, bool opt_continue) + :m_tocs(tocs), m_fromcs(fromcs), m_continue(opt_continue) + { } + size_t out_buffer_max_length(size_t from_length) const + { + return from_length / m_fromcs->mbminlen * m_tocs->mbmaxlen; + } + bool convert_data(const char *from, size_t length); + bool convert_binary_stream(FILE *file) + { + File_buffer buf; + return buf.load_binary_stream(file) || + convert_data(buf.ptr(), buf.length()); + } + bool convert_binary_file_by_name(const char *filename) + { + File_buffer buf; + return buf.load_binary_file_by_name(filename)|| + convert_data(buf.ptr(), buf.length()); + } +private: + void report_error(const char *from) const + { + if (well_formed_error_pos()) + { + fflush(stdout); + fprintf(stderr, + "Illegal %s byte sequence at position %d\n", + m_fromcs->cs_name.str, + (uint) (well_formed_error_pos() - from)); + } + else if (cannot_convert_error_pos()) + { + fflush(stdout); + fprintf(stderr, + "Conversion from %s to %s failed at position %d\n", + m_fromcs->cs_name.str, m_tocs->cs_name.str, + (uint) (cannot_convert_error_pos() - from)); + } + } + size_t write(const char *str, size_t length) const + { + return my_fwrite(stdout, (uchar *) str, length, MY_WME); + } +}; + + +bool Conv::convert_data(const char *from, size_t from_length) +{ + Conv_inbuf inbuf(from, from_length); + Conv_outbuf outbuf; + + if (outbuf.alloc(out_buffer_max_length(from_length))) + return true; + + for ( ; ; ) + { + LEX_CSTRING delim, data; + + delim= inbuf.get_delimiter_chunk(*this); + if (delim.length) + write(delim.str, delim.length); + + data= inbuf.get_data_chunk(*this); + if (!data.length) + break; + size_t length= well_formed_copy(m_tocs, + (char *) outbuf.ptr(), + outbuf.alloced_length(), + m_fromcs, data.str, data.length); + outbuf.length((uint32) length); + + if (most_important_error_pos() && !m_continue) + { + report_error(from); + return true; + } + write(outbuf.ptr(), outbuf.length()); + } + return false; +} + + +class Session +{ +public: + Session(const char *prog) + { + MY_INIT(prog); + } + ~Session() + { + my_end(0); + } + void usage(void) + { + printf("%s Ver %s Distrib %s for %s on %s\n", my_progname, CONV_VERSION, + MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); + puts("Character set conversion utility for MariaDB"); + puts("Usage:"); + printf("%s [OPTION...] [FILE...]\n", my_progname); + my_print_help(long_options); + } +}; + + +int main(int argc, char *argv[]) +{ + Session session(argv[0]); + CHARSET_INFO *charset_info_from= NULL; + CHARSET_INFO *charset_info_to= NULL; + + if (handle_options(&argc, &argv, long_options, get_one_option)) + { + session.usage(); + return 1; + } + + if (!(charset_info_from= opt.csinfo_from())) + { + fprintf(stderr, "Character set %s is not supported\n", opt.m_charset_from); + return 1; + } + + if (!(charset_info_to= opt.csinfo_to())) + { + fprintf(stderr, "Character set %s is not supported\n", opt.m_charset_to); + return 1; + } + + Conv conv(charset_info_to, charset_info_from, opt.m_continue); + if (opt.m_delimiter) + { + if (charset_info_from->mbminlen > 1 || + charset_info_to->mbminlen > 1) + { + fprintf(stderr, "--delimiter cannot be used with %s to %s conversion\n", + charset_info_from->cs_name.str, charset_info_to->cs_name.str); + return 1; + } + if (conv.set_delimiter_unescape(opt.m_delimiter)) + { + fprintf(stderr, "Bad --delimiter value\n"); + return 1; + } + } + + if (argc == 0) + { + if (conv.convert_binary_stream(stdin)) + return 1; + } + else + { + for (int i= 0; i < argc; i++) + { + if (conv.convert_binary_file_by_name(argv[i])) + return 1; + } + } + + return 0; +} /* main */ diff --git a/client/my_readline.h b/client/my_readline.h new file mode 100644 index 00000000..ec43d81f --- /dev/null +++ b/client/my_readline.h @@ -0,0 +1,42 @@ +#ifndef CLIENT_MY_READLINE_INCLUDED +#define CLIENT_MY_READLINE_INCLUDED + +/* + Copyright (c) 2000, 2011, 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 as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* readline for batch mode */ + +typedef struct st_line_buffer +{ + File file; + char *buffer; /* The buffer itself, grown as needed. */ + char *end; /* Pointer at buffer end */ + char *start_of_line,*end_of_line; + uint bufread; /* Number of bytes to get with each read(). */ + uint eof; + ulong max_size; + ulong read_length; /* Length of last read string */ + int error; + bool truncated; +} LINE_BUFFER; + +extern LINE_BUFFER *batch_readline_init(ulong max_size,FILE *file); +extern LINE_BUFFER *batch_readline_command(LINE_BUFFER *buffer, char * str); +extern char *batch_readline(LINE_BUFFER *buffer, bool binary_mode); +extern void batch_readline_end(LINE_BUFFER *buffer); + +#endif /* CLIENT_MY_READLINE_INCLUDED */ diff --git a/client/mysql.cc b/client/mysql.cc new file mode 100644 index 00000000..1c842dae --- /dev/null +++ b/client/mysql.cc @@ -0,0 +1,5610 @@ +/* + Copyright (c) 2000, 2018, Oracle and/or its affiliates. + Copyright (c) 2009, 2022, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* mysql command tool + * Commands compatible with mSQL by David J. Hughes + * + * Written by: + * Michael 'Monty' Widenius + * Andi Gutmans + * Zeev Suraski + * Jani Tolonen + * Matt Wagner + * Jeremy Cole + * Tonu Samuel + * Harrison Fisk + * + **/ + +#include "client_priv.h" +#include +#include +#include +#ifndef __GNU_LIBRARY__ +#define __GNU_LIBRARY__ // Skip warnings in getopt.h +#endif +#include "my_readline.h" +#include +#include +#include +#include +#if defined(HAVE_LOCALE_H) +#include +#endif + +const char *VER= "15.1"; + +/* 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= NULL; + +/* Array of options to pass to libemysqld */ +#define MAX_SERVER_ARGS 64 + +#include "sql_string.h" +#include "client_metadata.h" + +extern "C" { +#if defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) +#include +#include +#else +#if defined(HAVE_TERMIOS_H) +#include +#include +#elif defined(HAVE_TERMBITS_H) +#include +#elif defined(HAVE_ASM_TERMBITS_H) && (!defined __GLIBC__ || !(__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ > 0)) +#include // Standard linux +#endif +#undef VOID +#if defined(HAVE_TERMCAP_H) +#include +#else +#ifdef HAVE_CURSES_H +#include +#endif +#undef SYSV // hack to avoid syntax error +#ifdef HAVE_TERM_H +#include +#endif +#endif +#endif /* defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) */ + +#undef bcmp // Fix problem with new readline +#if !defined(_WIN32) +# ifdef __APPLE__ +# include +# else +# include +# if !defined(USE_LIBEDIT_INTERFACE) +# include +# endif +# endif +#define HAVE_READLINE +#endif +#define USE_POPEN +} + +static CHARSET_INFO *charset_info= &my_charset_latin1; + +#if defined(_WIN32) +/* + Set console mode for the whole duration of the client session. + + We need for input + - line input (i.e read lines from console) + - echo typed characters + - "cooked" mode, i.e we do not want to handle all keystrokes, + like DEL etc ourselves, yet. We might want handle keystrokes + in the future, to implement tab completion, and better + (multiline) history. + + Disable VT escapes for the output.We do not know what kind of escapes SELECT would return. +*/ +struct Console_mode +{ + HANDLE in= GetStdHandle(STD_INPUT_HANDLE); + HANDLE out= GetStdHandle(STD_OUTPUT_HANDLE); + DWORD mode_in=0; + DWORD mode_out=0; + + enum {STDIN_CHANGED = 1, STDOUT_CHANGED = 2}; + int changes=0; + + Console_mode() + { + if (in && in != INVALID_HANDLE_VALUE && GetConsoleMode(in, &mode_in)) + { + SetConsoleMode(in, ENABLE_ECHO_INPUT|ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT); + changes |= STDIN_CHANGED; + } + + if (out && out != INVALID_HANDLE_VALUE && GetConsoleMode(out, &mode_out)) + { +#ifdef ENABLE_VIRTUAL_TERMINAL_INPUT + SetConsoleMode(out, mode_out & ~ENABLE_VIRTUAL_TERMINAL_INPUT); + changes |= STDOUT_CHANGED; +#endif + } + } + + ~Console_mode() + { + if (changes & STDIN_CHANGED) + SetConsoleMode(in, mode_in); + + if(changes & STDOUT_CHANGED) + SetConsoleMode(out, mode_out); + } +}; + +static Console_mode my_conmode; + +#define MAX_CGETS_LINE_LEN 65535 +/** Read line from console, chomp EOL*/ +static char *win_readline() +{ + static wchar_t wstrbuf[MAX_CGETS_LINE_LEN]; + static char strbuf[MAX_CGETS_LINE_LEN * 4]; + + DWORD nchars= 0; + uint len= 0; + SetLastError(0); + if (!ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), wstrbuf, MAX_CGETS_LINE_LEN-1, + &nchars, NULL)) + goto err; + if (nchars == 0 && GetLastError() == ERROR_OPERATION_ABORTED) + goto err; + + for (;nchars > 0; nchars--) + { + if (wstrbuf[nchars - 1] != '\n' && wstrbuf[nchars - 1] != '\r') + break; + } + + if (nchars > 0) + { + uint errors; + len= my_convert(strbuf, sizeof(strbuf), charset_info, + (const char *) wstrbuf, nchars * sizeof(wchar_t), + &my_charset_utf16le_bin, &errors); + } + strbuf[len]= 0; + return strbuf; +err: + return NULL; +} +#endif + + +#ifdef HAVE_VIDATTR +static int have_curses= 0; +static void my_vidattr(chtype attrs) +{ + if (have_curses) + vidattr(attrs); +} +#else +#undef HAVE_SETUPTERM +#define my_vidattr(A) {} // Can't get this to work +#endif + +#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 "completion_hash.h" +#include // ORACLE_WELCOME_COPYRIGHT_NOTICE + +#define PROMPT_CHAR '\\' +#define DEFAULT_DELIMITER ";" + +#define MAX_BATCH_BUFFER_SIZE (1024L * 1024L * 1024L) + +typedef struct st_status +{ + int exit_status; + ulong query_start_line; + char *file_name; + LINE_BUFFER *line_buff; + bool batch,add_to_history; +} STATUS; + + +static HashTable ht; +static char **defaults_argv; + +enum enum_info_type { INFO_INFO,INFO_ERROR,INFO_RESULT}; +typedef enum enum_info_type INFO_TYPE; + +static MYSQL mysql; /* The connection */ +static my_bool ignore_errors=0,wait_flag=0,quick=0, + connected=0,opt_raw_data=0,unbuffered=0,output_tables=0, + opt_rehash=1,skip_updates=0,safe_updates=0,one_database=0, + opt_compress=0, using_opt_local_infile=0, + vertical=0, line_numbers=1, column_names=1,opt_html=0, + opt_xml=0,opt_nopager=1, opt_outfile=0, named_cmds= 0, + tty_password= 0, opt_nobeep=0, opt_reconnect=1, + opt_secure_auth= 0, + default_pager_set= 0, opt_sigint_ignore= 0, + auto_vertical_output= 0, + show_warnings= 0, executing_query= 0, + ignore_spaces= 0, opt_binhex= 0, opt_progress_reports; +static my_bool debug_info_flag, debug_check_flag, batch_abort_on_error; +static my_bool column_types_flag; +static my_bool preserve_comments= 0; +static my_bool in_com_source, aborted= 0; +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 my_end_arg; +static char * opt_mysql_unix_port=0; +static int connect_flag=CLIENT_INTERACTIVE; +static my_bool opt_binary_mode= FALSE; +static my_bool opt_connect_expired_password= FALSE; +static int interrupted_query= 0; +static char *current_host,*current_db,*current_user=0,*opt_password=0, + *current_prompt=0, *delimiter_str= 0, + *default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME, + *opt_init_command= 0; +static char *histfile; +static char *histfile_tmp; +static String glob_buffer,old_buffer; +static String processed_prompt; +static char *full_username=0,*part_username=0,*default_prompt=0; +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= 0, *opt_default_auth= 0; +static const char *xmlmeta[] = { + "&", "&", + "<", "<", + ">", ">", + "\"", """, + /* Turn \0 into a space. Why not �? That's not valid XML or HTML. */ + "\0", " ", + 0, 0 +}; +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; +static uint prompt_counter; +static char delimiter[16]= DEFAULT_DELIMITER; +static uint delimiter_length= 1; +unsigned short terminal_width= 80; + +static uint opt_protocol=0; +static const char *opt_protocol_type= ""; + +#include "sslopt-vars.h" + +const char *default_dbug_option="d:t:o,/tmp/mariadb.trace"; + +void tee_fprintf(FILE *file, const char *fmt, ...); +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,char **argv); +extern "C" my_bool get_one_option(int optid, const struct my_option *opt, + const char *argument); +static int com_quit(String *str,char*), + com_go(String *str,char*), com_ego(String *str,char*), + com_print(String *str,char*), + com_help(String *str,char*), com_clear(String *str,char*), + com_connect(String *str,char*), com_status(String *str,char*), + com_use(String *str,char*), com_source(String *str, char*), + com_rehash(String *str, char*), com_tee(String *str, char*), + com_notee(String *str, char*), com_charset(String *str,char*), + com_prompt(String *str, char*), com_delimiter(String *str, char*), + com_warnings(String *str, char*), com_nowarnings(String *str, char*); + +#ifdef USE_POPEN +static int com_nopager(String *str, char*), com_pager(String *str, char*), + com_edit(String *str,char*), com_shell(String *str, char *); +#endif + +static int read_and_execute(bool interactive); +static int sql_connect(char *host,char *database,char *user,char *password, + uint silent); +static const char *server_version_string(MYSQL *mysql); +static int put_info(const char *str,INFO_TYPE info,uint error=0, + const char *sql_state=0); +static int put_error(MYSQL *mysql); +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(); +enum get_arg_mode { CHECK, GET, GET_NEXT}; +static char *get_arg(char *line, get_arg_mode mode); +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); +#ifndef EMBEDDED_LIBRARY +static uint last_progress_report_length= 0; +static void report_progress(const MYSQL *mysql, uint stage, uint max_stage, + double progress, const char *proc_info, + uint proc_info_length); +#endif +static void report_progress_end(); + +/* 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; /* msql command character */ + 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, 1, "Synonym for `help'." }, + { "clear", 'c', com_clear, 0, "Clear the current input statement."}, + { "connect",'r', com_connect,1, + "Reconnect to the server. Optional arguments are db and host." }, + { "delimiter", 'd', com_delimiter, 1, + "Set statement delimiter." }, +#ifdef USE_POPEN + { "edit", 'e', com_edit, 0, "Edit command with $EDITOR."}, +#endif + { "ego", 'G', com_ego, 0, + "Send command to MariaDB server, display result vertically."}, + { "exit", 'q', com_quit, 0, "Exit mysql. Same as quit."}, + { "go", 'g', com_go, 0, "Send command to MariaDB server." }, + { "help", 'h', com_help, 1, "Display this help." }, +#ifdef USE_POPEN + { "nopager",'n', com_nopager,0, "Disable pager, print to stdout." }, +#endif + { "notee", 't', com_notee, 0, "Don't write into outfile." }, +#ifdef USE_POPEN + { "pager", 'P', com_pager, 1, + "Set PAGER [to_pager]. Print the query results via PAGER." }, +#endif + { "print", 'p', com_print, 0, "Print current command." }, + { "prompt", 'R', com_prompt, 1, "Change your mysql prompt."}, + { "quit", 'q', com_quit, 0, "Quit mysql." }, + { "rehash", '#', com_rehash, 0, "Rebuild completion hash." }, + { "source", '.', com_source, 1, + "Execute an SQL script file. Takes a file name as an argument."}, + { "status", 's', com_status, 0, "Get status information from the server."}, +#ifdef USE_POPEN + { "system", '!', com_shell, 1, "Execute a system shell command."}, +#endif + { "tee", 'T', com_tee, 1, + "Set outfile [to_outfile]. Append everything into given outfile." }, + { "use", 'u', com_use, 1, + "Use another database. Takes database name as argument." }, + { "charset", 'C', com_charset, 1, + "Switch to another charset. Might be needed for processing binlog with multi-byte charsets." }, + { "warnings", 'W', com_warnings, 0, + "Show warnings after every statement." }, + { "nowarning", 'w', com_nowarnings, 0, + "Don't show warnings after every statement." }, + /* Get bash-like expansion for some commands */ + { "create table", 0, 0, 0, ""}, + { "create database", 0, 0, 0, ""}, + { "show databases", 0, 0, 0, ""}, + { "show fields from", 0, 0, 0, ""}, + { "show keys from", 0, 0, 0, ""}, + { "show tables", 0, 0, 0, ""}, + { "load data from", 0, 0, 0, ""}, + { "alter table", 0, 0, 0, ""}, + { "set option", 0, 0, 0, ""}, + { "lock tables", 0, 0, 0, ""}, + { "unlock tables", 0, 0, 0, ""}, + /* generated 2006-12-28. Refresh occasionally from lexer. */ + { "ACTION", 0, 0, 0, ""}, + { "ADD", 0, 0, 0, ""}, + { "AFTER", 0, 0, 0, ""}, + { "AGAINST", 0, 0, 0, ""}, + { "AGGREGATE", 0, 0, 0, ""}, + { "ALL", 0, 0, 0, ""}, + { "ALGORITHM", 0, 0, 0, ""}, + { "ALTER", 0, 0, 0, ""}, + { "ANALYZE", 0, 0, 0, ""}, + { "AND", 0, 0, 0, ""}, + { "ANY", 0, 0, 0, ""}, + { "AS", 0, 0, 0, ""}, + { "ASC", 0, 0, 0, ""}, + { "ASCII", 0, 0, 0, ""}, + { "ASENSITIVE", 0, 0, 0, ""}, + { "AUTO_INCREMENT", 0, 0, 0, ""}, + { "AVG", 0, 0, 0, ""}, + { "AVG_ROW_LENGTH", 0, 0, 0, ""}, + { "BACKUP", 0, 0, 0, ""}, + { "BDB", 0, 0, 0, ""}, + { "BEFORE", 0, 0, 0, ""}, + { "BEGIN", 0, 0, 0, ""}, + { "BERKELEYDB", 0, 0, 0, ""}, + { "BETWEEN", 0, 0, 0, ""}, + { "BIGINT", 0, 0, 0, ""}, + { "BINARY", 0, 0, 0, ""}, + { "BINLOG", 0, 0, 0, ""}, + { "BIT", 0, 0, 0, ""}, + { "BLOB", 0, 0, 0, ""}, + { "BOOL", 0, 0, 0, ""}, + { "BOOLEAN", 0, 0, 0, ""}, + { "BOTH", 0, 0, 0, ""}, + { "BTREE", 0, 0, 0, ""}, + { "BY", 0, 0, 0, ""}, + { "BYTE", 0, 0, 0, ""}, + { "CACHE", 0, 0, 0, ""}, + { "CALL", 0, 0, 0, ""}, + { "CASCADE", 0, 0, 0, ""}, + { "CASCADED", 0, 0, 0, ""}, + { "CASE", 0, 0, 0, ""}, + { "CHAIN", 0, 0, 0, ""}, + { "CHANGE", 0, 0, 0, ""}, + { "CHANGED", 0, 0, 0, ""}, + { "CHAR", 0, 0, 0, ""}, + { "CHARACTER", 0, 0, 0, ""}, + { "CHARSET", 0, 0, 0, ""}, + { "CHECK", 0, 0, 0, ""}, + { "CHECKSUM", 0, 0, 0, ""}, + { "CIPHER", 0, 0, 0, ""}, + { "CLIENT", 0, 0, 0, ""}, + { "CLOSE", 0, 0, 0, ""}, + { "CODE", 0, 0, 0, ""}, + { "COLLATE", 0, 0, 0, ""}, + { "COLLATION", 0, 0, 0, ""}, + { "COLUMN", 0, 0, 0, ""}, + { "COLUMNS", 0, 0, 0, ""}, + { "COMMENT", 0, 0, 0, ""}, + { "COMMIT", 0, 0, 0, ""}, + { "COMMITTED", 0, 0, 0, ""}, + { "COMPACT", 0, 0, 0, ""}, + { "COMPRESSED", 0, 0, 0, ""}, + { "CONCURRENT", 0, 0, 0, ""}, + { "CONDITION", 0, 0, 0, ""}, + { "CONNECTION", 0, 0, 0, ""}, + { "CONSISTENT", 0, 0, 0, ""}, + { "CONSTRAINT", 0, 0, 0, ""}, + { "CONTAINS", 0, 0, 0, ""}, + { "CONTINUE", 0, 0, 0, ""}, + { "CONVERT", 0, 0, 0, ""}, + { "CREATE", 0, 0, 0, ""}, + { "CROSS", 0, 0, 0, ""}, + { "CUBE", 0, 0, 0, ""}, + { "CURRENT_DATE", 0, 0, 0, ""}, + { "CURRENT_TIME", 0, 0, 0, ""}, + { "CURRENT_TIMESTAMP", 0, 0, 0, ""}, + { "CURRENT_USER", 0, 0, 0, ""}, + { "CURSOR", 0, 0, 0, ""}, + { "DATA", 0, 0, 0, ""}, + { "DATABASE", 0, 0, 0, ""}, + { "DATABASES", 0, 0, 0, ""}, + { "DATE", 0, 0, 0, ""}, + { "DATETIME", 0, 0, 0, ""}, + { "DAY", 0, 0, 0, ""}, + { "DAY_HOUR", 0, 0, 0, ""}, + { "DAY_MICROSECOND", 0, 0, 0, ""}, + { "DAY_MINUTE", 0, 0, 0, ""}, + { "DAY_SECOND", 0, 0, 0, ""}, + { "DEALLOCATE", 0, 0, 0, ""}, + { "DEC", 0, 0, 0, ""}, + { "DECIMAL", 0, 0, 0, ""}, + { "DECLARE", 0, 0, 0, ""}, + { "DEFAULT", 0, 0, 0, ""}, + { "DEFINER", 0, 0, 0, ""}, + { "DELAYED", 0, 0, 0, ""}, + { "DELAY_KEY_WRITE", 0, 0, 0, ""}, + { "DELETE", 0, 0, 0, ""}, + { "DESC", 0, 0, 0, ""}, + { "DESCRIBE", 0, 0, 0, ""}, + { "DES_KEY_FILE", 0, 0, 0, ""}, + { "DETERMINISTIC", 0, 0, 0, ""}, + { "DIRECTORY", 0, 0, 0, ""}, + { "DISABLE", 0, 0, 0, ""}, + { "DISCARD", 0, 0, 0, ""}, + { "DISTINCT", 0, 0, 0, ""}, + { "DISTINCTROW", 0, 0, 0, ""}, + { "DIV", 0, 0, 0, ""}, + { "DO", 0, 0, 0, ""}, + { "DOUBLE", 0, 0, 0, ""}, + { "DROP", 0, 0, 0, ""}, + { "DUAL", 0, 0, 0, ""}, + { "DUMPFILE", 0, 0, 0, ""}, + { "DUPLICATE", 0, 0, 0, ""}, + { "DYNAMIC", 0, 0, 0, ""}, + { "EACH", 0, 0, 0, ""}, + { "ELSE", 0, 0, 0, ""}, + { "ELSEIF", 0, 0, 0, ""}, + { "ENABLE", 0, 0, 0, ""}, + { "ENCLOSED", 0, 0, 0, ""}, + { "END", 0, 0, 0, ""}, + { "ENGINE", 0, 0, 0, ""}, + { "ENGINES", 0, 0, 0, ""}, + { "ENUM", 0, 0, 0, ""}, + { "ERRORS", 0, 0, 0, ""}, + { "ESCAPE", 0, 0, 0, ""}, + { "ESCAPED", 0, 0, 0, ""}, + { "EVENTS", 0, 0, 0, ""}, + { "EXECUTE", 0, 0, 0, ""}, + { "EXISTS", 0, 0, 0, ""}, + { "EXIT", 0, 0, 0, ""}, + { "EXPANSION", 0, 0, 0, ""}, + { "EXPLAIN", 0, 0, 0, ""}, + { "EXTENDED", 0, 0, 0, ""}, + { "FALSE", 0, 0, 0, ""}, + { "FAST", 0, 0, 0, ""}, + { "FETCH", 0, 0, 0, ""}, + { "FIELDS", 0, 0, 0, ""}, + { "FILE", 0, 0, 0, ""}, + { "FIRST", 0, 0, 0, ""}, + { "FIXED", 0, 0, 0, ""}, + { "FLOAT", 0, 0, 0, ""}, + { "FLOAT4", 0, 0, 0, ""}, + { "FLOAT8", 0, 0, 0, ""}, + { "FLUSH", 0, 0, 0, ""}, + { "FOR", 0, 0, 0, ""}, + { "FORCE", 0, 0, 0, ""}, + { "FOREIGN", 0, 0, 0, ""}, + { "FOUND", 0, 0, 0, ""}, + { "FROM", 0, 0, 0, ""}, + { "FULL", 0, 0, 0, ""}, + { "FULLTEXT", 0, 0, 0, ""}, + { "FUNCTION", 0, 0, 0, ""}, + { "GEOMETRY", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTION", 0, 0, 0, ""}, + { "GET_FORMAT", 0, 0, 0, ""}, + { "GLOBAL", 0, 0, 0, ""}, + { "GRANT", 0, 0, 0, ""}, + { "GRANTS", 0, 0, 0, ""}, + { "GROUP", 0, 0, 0, ""}, + { "HANDLER", 0, 0, 0, ""}, + { "HASH", 0, 0, 0, ""}, + { "HAVING", 0, 0, 0, ""}, + { "HELP", 0, 0, 0, ""}, + { "HIGH_PRIORITY", 0, 0, 0, ""}, + { "HOSTS", 0, 0, 0, ""}, + { "HOUR", 0, 0, 0, ""}, + { "HOUR_MICROSECOND", 0, 0, 0, ""}, + { "HOUR_MINUTE", 0, 0, 0, ""}, + { "HOUR_SECOND", 0, 0, 0, ""}, + { "IDENTIFIED", 0, 0, 0, ""}, + { "IF", 0, 0, 0, ""}, + { "IGNORE", 0, 0, 0, ""}, + { "IMPORT", 0, 0, 0, ""}, + { "IN", 0, 0, 0, ""}, + { "INDEX", 0, 0, 0, ""}, + { "INDEXES", 0, 0, 0, ""}, + { "INFILE", 0, 0, 0, ""}, + { "INNER", 0, 0, 0, ""}, + { "INNOBASE", 0, 0, 0, ""}, + { "INNODB", 0, 0, 0, ""}, + { "INOUT", 0, 0, 0, ""}, + { "INSENSITIVE", 0, 0, 0, ""}, + { "INSERT", 0, 0, 0, ""}, + { "INSERT_METHOD", 0, 0, 0, ""}, + { "INT", 0, 0, 0, ""}, + { "INT1", 0, 0, 0, ""}, + { "INT2", 0, 0, 0, ""}, + { "INT3", 0, 0, 0, ""}, + { "INT4", 0, 0, 0, ""}, + { "INT8", 0, 0, 0, ""}, + { "INTEGER", 0, 0, 0, ""}, + { "INTERVAL", 0, 0, 0, ""}, + { "INTO", 0, 0, 0, ""}, + { "IO_THREAD", 0, 0, 0, ""}, + { "IS", 0, 0, 0, ""}, + { "ISOLATION", 0, 0, 0, ""}, + { "ISSUER", 0, 0, 0, ""}, + { "ITERATE", 0, 0, 0, ""}, + { "INVOKER", 0, 0, 0, ""}, + { "JOIN", 0, 0, 0, ""}, + { "KEY", 0, 0, 0, ""}, + { "KEYS", 0, 0, 0, ""}, + { "KILL", 0, 0, 0, ""}, + { "LANGUAGE", 0, 0, 0, ""}, + { "LAST", 0, 0, 0, ""}, + { "LEADING", 0, 0, 0, ""}, + { "LEAVE", 0, 0, 0, ""}, + { "LEAVES", 0, 0, 0, ""}, + { "LEFT", 0, 0, 0, ""}, + { "LEVEL", 0, 0, 0, ""}, + { "LIKE", 0, 0, 0, ""}, + { "LIMIT", 0, 0, 0, ""}, + { "LINES", 0, 0, 0, ""}, + { "LINESTRING", 0, 0, 0, ""}, + { "LOAD", 0, 0, 0, ""}, + { "LOCAL", 0, 0, 0, ""}, + { "LOCALTIME", 0, 0, 0, ""}, + { "LOCALTIMESTAMP", 0, 0, 0, ""}, + { "LOCK", 0, 0, 0, ""}, + { "LOCKS", 0, 0, 0, ""}, + { "LOGS", 0, 0, 0, ""}, + { "LONG", 0, 0, 0, ""}, + { "LONGBLOB", 0, 0, 0, ""}, + { "LONGTEXT", 0, 0, 0, ""}, + { "LOOP", 0, 0, 0, ""}, + { "LOW_PRIORITY", 0, 0, 0, ""}, + { "MASTER", 0, 0, 0, ""}, + { "MASTER_CONNECT_RETRY", 0, 0, 0, ""}, + { "MASTER_HOST", 0, 0, 0, ""}, + { "MASTER_LOG_FILE", 0, 0, 0, ""}, + { "MASTER_LOG_POS", 0, 0, 0, ""}, + { "MASTER_PASSWORD", 0, 0, 0, ""}, + { "MASTER_PORT", 0, 0, 0, ""}, + { "MASTER_SERVER_ID", 0, 0, 0, ""}, + { "MASTER_SSL", 0, 0, 0, ""}, + { "MASTER_SSL_CA", 0, 0, 0, ""}, + { "MASTER_SSL_CAPATH", 0, 0, 0, ""}, + { "MASTER_SSL_CERT", 0, 0, 0, ""}, + { "MASTER_SSL_CIPHER", 0, 0, 0, ""}, + { "MASTER_SSL_KEY", 0, 0, 0, ""}, + { "MASTER_USER", 0, 0, 0, ""}, + { "MATCH", 0, 0, 0, ""}, + { "MAX_CONNECTIONS_PER_HOUR", 0, 0, 0, ""}, + { "MAX_QUERIES_PER_HOUR", 0, 0, 0, ""}, + { "MAX_ROWS", 0, 0, 0, ""}, + { "MAX_UPDATES_PER_HOUR", 0, 0, 0, ""}, + { "MAX_USER_CONNECTIONS", 0, 0, 0, ""}, + { "MEDIUM", 0, 0, 0, ""}, + { "MEDIUMBLOB", 0, 0, 0, ""}, + { "MEDIUMINT", 0, 0, 0, ""}, + { "MEDIUMTEXT", 0, 0, 0, ""}, + { "MERGE", 0, 0, 0, ""}, + { "MICROSECOND", 0, 0, 0, ""}, + { "MIDDLEINT", 0, 0, 0, ""}, + { "MIGRATE", 0, 0, 0, ""}, + { "MINUTE", 0, 0, 0, ""}, + { "MINUTE_MICROSECOND", 0, 0, 0, ""}, + { "MINUTE_SECOND", 0, 0, 0, ""}, + { "MIN_ROWS", 0, 0, 0, ""}, + { "MOD", 0, 0, 0, ""}, + { "MODE", 0, 0, 0, ""}, + { "MODIFIES", 0, 0, 0, ""}, + { "MODIFY", 0, 0, 0, ""}, + { "MONTH", 0, 0, 0, ""}, + { "MULTILINESTRING", 0, 0, 0, ""}, + { "MULTIPOINT", 0, 0, 0, ""}, + { "MULTIPOLYGON", 0, 0, 0, ""}, + { "MUTEX", 0, 0, 0, ""}, + { "NAME", 0, 0, 0, ""}, + { "NAMES", 0, 0, 0, ""}, + { "NATIONAL", 0, 0, 0, ""}, + { "NATURAL", 0, 0, 0, ""}, + { "NCHAR", 0, 0, 0, ""}, + { "NEW", 0, 0, 0, ""}, + { "NEXT", 0, 0, 0, ""}, + { "NO", 0, 0, 0, ""}, + { "NONE", 0, 0, 0, ""}, + { "NOT", 0, 0, 0, ""}, + { "NO_WRITE_TO_BINLOG", 0, 0, 0, ""}, + { "NULL", 0, 0, 0, ""}, + { "NUMERIC", 0, 0, 0, ""}, + { "NVARCHAR", 0, 0, 0, ""}, + { "OFFSET", 0, 0, 0, ""}, + { "OLD_PASSWORD", 0, 0, 0, ""}, + { "ON", 0, 0, 0, ""}, + { "ONE", 0, 0, 0, ""}, + { "OPEN", 0, 0, 0, ""}, + { "OPTIMIZE", 0, 0, 0, ""}, + { "OPTION", 0, 0, 0, ""}, + { "OPTIONALLY", 0, 0, 0, ""}, + { "OR", 0, 0, 0, ""}, + { "ORDER", 0, 0, 0, ""}, + { "OUT", 0, 0, 0, ""}, + { "OUTER", 0, 0, 0, ""}, + { "OUTFILE", 0, 0, 0, ""}, + { "PACK_KEYS", 0, 0, 0, ""}, + { "PARTIAL", 0, 0, 0, ""}, + { "PASSWORD", 0, 0, 0, ""}, + { "PHASE", 0, 0, 0, ""}, + { "POINT", 0, 0, 0, ""}, + { "POLYGON", 0, 0, 0, ""}, + { "PRECISION", 0, 0, 0, ""}, + { "PREPARE", 0, 0, 0, ""}, + { "PREV", 0, 0, 0, ""}, + { "PRIMARY", 0, 0, 0, ""}, + { "PRIVILEGES", 0, 0, 0, ""}, + { "PROCEDURE", 0, 0, 0, ""}, + { "PROCESS", 0, 0, 0, ""}, + { "PROCESSLIST", 0, 0, 0, ""}, + { "PURGE", 0, 0, 0, ""}, + { "QUARTER", 0, 0, 0, ""}, + { "QUERY", 0, 0, 0, ""}, + { "QUICK", 0, 0, 0, ""}, + { "READ", 0, 0, 0, ""}, + { "READS", 0, 0, 0, ""}, + { "REAL", 0, 0, 0, ""}, + { "RECOVER", 0, 0, 0, ""}, + { "REDUNDANT", 0, 0, 0, ""}, + { "REFERENCES", 0, 0, 0, ""}, + { "REGEXP", 0, 0, 0, ""}, + { "RELAY_LOG_FILE", 0, 0, 0, ""}, + { "RELAY_LOG_POS", 0, 0, 0, ""}, + { "RELAY_THREAD", 0, 0, 0, ""}, + { "RELEASE", 0, 0, 0, ""}, + { "RELOAD", 0, 0, 0, ""}, + { "RENAME", 0, 0, 0, ""}, + { "REPAIR", 0, 0, 0, ""}, + { "REPEATABLE", 0, 0, 0, ""}, + { "REPLACE", 0, 0, 0, ""}, + { "REPLICATION", 0, 0, 0, ""}, + { "REPEAT", 0, 0, 0, ""}, + { "REQUIRE", 0, 0, 0, ""}, + { "RESET", 0, 0, 0, ""}, + { "RESTORE", 0, 0, 0, ""}, + { "RESTRICT", 0, 0, 0, ""}, + { "RESUME", 0, 0, 0, ""}, + { "RETURN", 0, 0, 0, ""}, + { "RETURNS", 0, 0, 0, ""}, + { "REVOKE", 0, 0, 0, ""}, + { "RIGHT", 0, 0, 0, ""}, + { "RLIKE", 0, 0, 0, ""}, + { "ROLLBACK", 0, 0, 0, ""}, + { "ROLLUP", 0, 0, 0, ""}, + { "ROUTINE", 0, 0, 0, ""}, + { "ROW", 0, 0, 0, ""}, + { "ROWS", 0, 0, 0, ""}, + { "ROW_FORMAT", 0, 0, 0, ""}, + { "RTREE", 0, 0, 0, ""}, + { "SAVEPOINT", 0, 0, 0, ""}, + { "SCHEMA", 0, 0, 0, ""}, + { "SCHEMAS", 0, 0, 0, ""}, + { "SECOND", 0, 0, 0, ""}, + { "SECOND_MICROSECOND", 0, 0, 0, ""}, + { "SECURITY", 0, 0, 0, ""}, + { "SELECT", 0, 0, 0, ""}, + { "SENSITIVE", 0, 0, 0, ""}, + { "SEPARATOR", 0, 0, 0, ""}, + { "SERIAL", 0, 0, 0, ""}, + { "SERIALIZABLE", 0, 0, 0, ""}, + { "SESSION", 0, 0, 0, ""}, + { "SET", 0, 0, 0, ""}, + { "SHARE", 0, 0, 0, ""}, + { "SHOW", 0, 0, 0, ""}, + { "SHUTDOWN", 0, 0, 0, ""}, + { "SIGNED", 0, 0, 0, ""}, + { "SIMPLE", 0, 0, 0, ""}, + { "SLAVE", 0, 0, 0, ""}, + { "SNAPSHOT", 0, 0, 0, ""}, + { "SMALLINT", 0, 0, 0, ""}, + { "SOME", 0, 0, 0, ""}, + { "SONAME", 0, 0, 0, ""}, + { "SOUNDS", 0, 0, 0, ""}, + { "SPATIAL", 0, 0, 0, ""}, + { "SPECIFIC", 0, 0, 0, ""}, + { "SQL", 0, 0, 0, ""}, + { "SQLEXCEPTION", 0, 0, 0, ""}, + { "SQLSTATE", 0, 0, 0, ""}, + { "SQLWARNING", 0, 0, 0, ""}, + { "SQL_BIG_RESULT", 0, 0, 0, ""}, + { "SQL_BUFFER_RESULT", 0, 0, 0, ""}, + { "SQL_CACHE", 0, 0, 0, ""}, + { "SQL_CALC_FOUND_ROWS", 0, 0, 0, ""}, + { "SQL_NO_CACHE", 0, 0, 0, ""}, + { "SQL_SMALL_RESULT", 0, 0, 0, ""}, + { "SQL_THREAD", 0, 0, 0, ""}, + { "SQL_TSI_SECOND", 0, 0, 0, ""}, + { "SQL_TSI_MINUTE", 0, 0, 0, ""}, + { "SQL_TSI_HOUR", 0, 0, 0, ""}, + { "SQL_TSI_DAY", 0, 0, 0, ""}, + { "SQL_TSI_WEEK", 0, 0, 0, ""}, + { "SQL_TSI_MONTH", 0, 0, 0, ""}, + { "SQL_TSI_QUARTER", 0, 0, 0, ""}, + { "SQL_TSI_YEAR", 0, 0, 0, ""}, + { "SSL", 0, 0, 0, ""}, + { "START", 0, 0, 0, ""}, + { "STARTING", 0, 0, 0, ""}, + { "STATUS", 0, 0, 0, ""}, + { "STOP", 0, 0, 0, ""}, + { "STORAGE", 0, 0, 0, ""}, + { "STRAIGHT_JOIN", 0, 0, 0, ""}, + { "STRING", 0, 0, 0, ""}, + { "STRIPED", 0, 0, 0, ""}, + { "SUBJECT", 0, 0, 0, ""}, + { "SUPER", 0, 0, 0, ""}, + { "SUSPEND", 0, 0, 0, ""}, + { "TABLE", 0, 0, 0, ""}, + { "TABLES", 0, 0, 0, ""}, + { "TABLESPACE", 0, 0, 0, ""}, + { "TEMPORARY", 0, 0, 0, ""}, + { "TEMPTABLE", 0, 0, 0, ""}, + { "TERMINATED", 0, 0, 0, ""}, + { "TEXT", 0, 0, 0, ""}, + { "THEN", 0, 0, 0, ""}, + { "TIME", 0, 0, 0, ""}, + { "TIMESTAMP", 0, 0, 0, ""}, + { "TIMESTAMPADD", 0, 0, 0, ""}, + { "TIMESTAMPDIFF", 0, 0, 0, ""}, + { "TINYBLOB", 0, 0, 0, ""}, + { "TINYINT", 0, 0, 0, ""}, + { "TINYTEXT", 0, 0, 0, ""}, + { "TO", 0, 0, 0, ""}, + { "TRAILING", 0, 0, 0, ""}, + { "TRANSACTION", 0, 0, 0, ""}, + { "TRIGGER", 0, 0, 0, ""}, + { "TRIGGERS", 0, 0, 0, ""}, + { "TRUE", 0, 0, 0, ""}, + { "TRUNCATE", 0, 0, 0, ""}, + { "TYPE", 0, 0, 0, ""}, + { "TYPES", 0, 0, 0, ""}, + { "UNCOMMITTED", 0, 0, 0, ""}, + { "UNDEFINED", 0, 0, 0, ""}, + { "UNDO", 0, 0, 0, ""}, + { "UNICODE", 0, 0, 0, ""}, + { "UNION", 0, 0, 0, ""}, + { "UNIQUE", 0, 0, 0, ""}, + { "UNKNOWN", 0, 0, 0, ""}, + { "UNLOCK", 0, 0, 0, ""}, + { "UNSIGNED", 0, 0, 0, ""}, + { "UNTIL", 0, 0, 0, ""}, + { "UPDATE", 0, 0, 0, ""}, + { "UPGRADE", 0, 0, 0, ""}, + { "USAGE", 0, 0, 0, ""}, + { "USE", 0, 0, 0, ""}, + { "USER", 0, 0, 0, ""}, + { "USER_RESOURCES", 0, 0, 0, ""}, + { "USE_FRM", 0, 0, 0, ""}, + { "USING", 0, 0, 0, ""}, + { "UTC_DATE", 0, 0, 0, ""}, + { "UTC_TIME", 0, 0, 0, ""}, + { "UTC_TIMESTAMP", 0, 0, 0, ""}, + { "VALUE", 0, 0, 0, ""}, + { "VALUES", 0, 0, 0, ""}, + { "VARBINARY", 0, 0, 0, ""}, + { "VARCHAR", 0, 0, 0, ""}, + { "VARCHARACTER", 0, 0, 0, ""}, + { "VARIABLES", 0, 0, 0, ""}, + { "VARYING", 0, 0, 0, ""}, + { "WARNINGS", 0, 0, 0, ""}, + { "WEEK", 0, 0, 0, ""}, + { "WHEN", 0, 0, 0, ""}, + { "WHERE", 0, 0, 0, ""}, + { "WHILE", 0, 0, 0, ""}, + { "VIEW", 0, 0, 0, ""}, + { "WITH", 0, 0, 0, ""}, + { "WORK", 0, 0, 0, ""}, + { "WRITE", 0, 0, 0, ""}, + { "X509", 0, 0, 0, ""}, + { "XOR", 0, 0, 0, ""}, + { "XA", 0, 0, 0, ""}, + { "YEAR", 0, 0, 0, ""}, + { "YEAR_MONTH", 0, 0, 0, ""}, + { "ZEROFILL", 0, 0, 0, ""}, + { "ABS", 0, 0, 0, ""}, + { "ACOS", 0, 0, 0, ""}, + { "ADDDATE", 0, 0, 0, ""}, + { "ADDTIME", 0, 0, 0, ""}, + { "AES_ENCRYPT", 0, 0, 0, ""}, + { "AES_DECRYPT", 0, 0, 0, ""}, + { "AREA", 0, 0, 0, ""}, + { "ASIN", 0, 0, 0, ""}, + { "ASBINARY", 0, 0, 0, ""}, + { "ASTEXT", 0, 0, 0, ""}, + { "ASWKB", 0, 0, 0, ""}, + { "ASWKT", 0, 0, 0, ""}, + { "ATAN", 0, 0, 0, ""}, + { "ATAN2", 0, 0, 0, ""}, + { "BENCHMARK", 0, 0, 0, ""}, + { "BIN", 0, 0, 0, ""}, + { "BIT_COUNT", 0, 0, 0, ""}, + { "BIT_OR", 0, 0, 0, ""}, + { "BIT_AND", 0, 0, 0, ""}, + { "BIT_XOR", 0, 0, 0, ""}, + { "CAST", 0, 0, 0, ""}, + { "CEIL", 0, 0, 0, ""}, + { "CEILING", 0, 0, 0, ""}, + { "BIT_LENGTH", 0, 0, 0, ""}, + { "CENTROID", 0, 0, 0, ""}, + { "CHAR_LENGTH", 0, 0, 0, ""}, + { "CHARACTER_LENGTH", 0, 0, 0, ""}, + { "COALESCE", 0, 0, 0, ""}, + { "COERCIBILITY", 0, 0, 0, ""}, + { "COMPRESS", 0, 0, 0, ""}, + { "CONCAT", 0, 0, 0, ""}, + { "CONCAT_WS", 0, 0, 0, ""}, + { "CONNECTION_ID", 0, 0, 0, ""}, + { "CONV", 0, 0, 0, ""}, + { "CONVERT_TZ", 0, 0, 0, ""}, + { "COUNT", 0, 0, 0, ""}, + { "COS", 0, 0, 0, ""}, + { "COT", 0, 0, 0, ""}, + { "CRC32", 0, 0, 0, ""}, + { "CROSSES", 0, 0, 0, ""}, + { "CURDATE", 0, 0, 0, ""}, + { "CURTIME", 0, 0, 0, ""}, + { "DATE_ADD", 0, 0, 0, ""}, + { "DATEDIFF", 0, 0, 0, ""}, + { "DATE_FORMAT", 0, 0, 0, ""}, + { "DATE_SUB", 0, 0, 0, ""}, + { "DAYNAME", 0, 0, 0, ""}, + { "DAYOFMONTH", 0, 0, 0, ""}, + { "DAYOFWEEK", 0, 0, 0, ""}, + { "DAYOFYEAR", 0, 0, 0, ""}, + { "DECODE", 0, 0, 0, ""}, + { "DEGREES", 0, 0, 0, ""}, + { "DES_ENCRYPT", 0, 0, 0, ""}, + { "DES_DECRYPT", 0, 0, 0, ""}, + { "DIMENSION", 0, 0, 0, ""}, + { "DISJOINT", 0, 0, 0, ""}, + { "ELT", 0, 0, 0, ""}, + { "ENCODE", 0, 0, 0, ""}, + { "ENCRYPT", 0, 0, 0, ""}, + { "ENDPOINT", 0, 0, 0, ""}, + { "ENVELOPE", 0, 0, 0, ""}, + { "EQUALS", 0, 0, 0, ""}, + { "EXTERIORRING", 0, 0, 0, ""}, + { "EXTRACT", 0, 0, 0, ""}, + { "EXP", 0, 0, 0, ""}, + { "EXPORT_SET", 0, 0, 0, ""}, + { "FIELD", 0, 0, 0, ""}, + { "FIND_IN_SET", 0, 0, 0, ""}, + { "FLOOR", 0, 0, 0, ""}, + { "FORMAT", 0, 0, 0, ""}, + { "FOUND_ROWS", 0, 0, 0, ""}, + { "FROM_DAYS", 0, 0, 0, ""}, + { "FROM_UNIXTIME", 0, 0, 0, ""}, + { "GET_LOCK", 0, 0, 0, ""}, + { "GEOMETRYN", 0, 0, 0, ""}, + { "GEOMETRYTYPE", 0, 0, 0, ""}, + { "GEOMCOLLFROMTEXT", 0, 0, 0, ""}, + { "GEOMCOLLFROMWKB", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTIONFROMTEXT", 0, 0, 0, ""}, + { "GEOMETRYCOLLECTIONFROMWKB", 0, 0, 0, ""}, + { "GEOMETRYFROMTEXT", 0, 0, 0, ""}, + { "GEOMETRYFROMWKB", 0, 0, 0, ""}, + { "GEOMFROMTEXT", 0, 0, 0, ""}, + { "GEOMFROMWKB", 0, 0, 0, ""}, + { "GLENGTH", 0, 0, 0, ""}, + { "GREATEST", 0, 0, 0, ""}, + { "GROUP_CONCAT", 0, 0, 0, ""}, + { "GROUP_UNIQUE_USERS", 0, 0, 0, ""}, + { "HEX", 0, 0, 0, ""}, + { "IFNULL", 0, 0, 0, ""}, + { "INET_ATON", 0, 0, 0, ""}, + { "INET_NTOA", 0, 0, 0, ""}, + { "INSTR", 0, 0, 0, ""}, + { "INTERIORRINGN", 0, 0, 0, ""}, + { "INTERSECTS", 0, 0, 0, ""}, + { "ISCLOSED", 0, 0, 0, ""}, + { "ISEMPTY", 0, 0, 0, ""}, + { "ISNULL", 0, 0, 0, ""}, + { "IS_FREE_LOCK", 0, 0, 0, ""}, + { "IS_USED_LOCK", 0, 0, 0, ""}, + { "LAST_INSERT_ID", 0, 0, 0, ""}, + { "ISSIMPLE", 0, 0, 0, ""}, + { "LAST_DAY", 0, 0, 0, ""}, + { "LAST_VALUE", 0, 0, 0, ""}, + { "LCASE", 0, 0, 0, ""}, + { "LEAST", 0, 0, 0, ""}, + { "LENGTH", 0, 0, 0, ""}, + { "LN", 0, 0, 0, ""}, + { "LINEFROMTEXT", 0, 0, 0, ""}, + { "LINEFROMWKB", 0, 0, 0, ""}, + { "LINESTRINGFROMTEXT", 0, 0, 0, ""}, + { "LINESTRINGFROMWKB", 0, 0, 0, ""}, + { "LOAD_FILE", 0, 0, 0, ""}, + { "LOCATE", 0, 0, 0, ""}, + { "LOG", 0, 0, 0, ""}, + { "LOG2", 0, 0, 0, ""}, + { "LOG10", 0, 0, 0, ""}, + { "LOWER", 0, 0, 0, ""}, + { "LPAD", 0, 0, 0, ""}, + { "LTRIM", 0, 0, 0, ""}, + { "MAKE_SET", 0, 0, 0, ""}, + { "MAKEDATE", 0, 0, 0, ""}, + { "MAKETIME", 0, 0, 0, ""}, + { "MASTER_GTID_WAIT", 0, 0, 0, ""}, + { "MASTER_POS_WAIT", 0, 0, 0, ""}, + { "MAX", 0, 0, 0, ""}, + { "MBRCONTAINS", 0, 0, 0, ""}, + { "MBRDISJOINT", 0, 0, 0, ""}, + { "MBREQUAL", 0, 0, 0, ""}, + { "MBRINTERSECTS", 0, 0, 0, ""}, + { "MBROVERLAPS", 0, 0, 0, ""}, + { "MBRTOUCHES", 0, 0, 0, ""}, + { "MBRWITHIN", 0, 0, 0, ""}, + { "MD5", 0, 0, 0, ""}, + { "MID", 0, 0, 0, ""}, + { "MIN", 0, 0, 0, ""}, + { "MLINEFROMTEXT", 0, 0, 0, ""}, + { "MLINEFROMWKB", 0, 0, 0, ""}, + { "MPOINTFROMTEXT", 0, 0, 0, ""}, + { "MPOINTFROMWKB", 0, 0, 0, ""}, + { "MPOLYFROMTEXT", 0, 0, 0, ""}, + { "MPOLYFROMWKB", 0, 0, 0, ""}, + { "MONTHNAME", 0, 0, 0, ""}, + { "MULTILINESTRINGFROMTEXT", 0, 0, 0, ""}, + { "MULTILINESTRINGFROMWKB", 0, 0, 0, ""}, + { "MULTIPOINTFROMTEXT", 0, 0, 0, ""}, + { "MULTIPOINTFROMWKB", 0, 0, 0, ""}, + { "MULTIPOLYGONFROMTEXT", 0, 0, 0, ""}, + { "MULTIPOLYGONFROMWKB", 0, 0, 0, ""}, + { "NAME_CONST", 0, 0, 0, ""}, + { "NOW", 0, 0, 0, ""}, + { "NULLIF", 0, 0, 0, ""}, + { "NUMGEOMETRIES", 0, 0, 0, ""}, + { "NUMINTERIORRINGS", 0, 0, 0, ""}, + { "NUMPOINTS", 0, 0, 0, ""}, + { "OCTET_LENGTH", 0, 0, 0, ""}, + { "OCT", 0, 0, 0, ""}, + { "ORD", 0, 0, 0, ""}, + { "OVERLAPS", 0, 0, 0, ""}, + { "PERIOD_ADD", 0, 0, 0, ""}, + { "PERIOD_DIFF", 0, 0, 0, ""}, + { "PI", 0, 0, 0, ""}, + { "POINTFROMTEXT", 0, 0, 0, ""}, + { "POINTFROMWKB", 0, 0, 0, ""}, + { "POINTN", 0, 0, 0, ""}, + { "POLYFROMTEXT", 0, 0, 0, ""}, + { "POLYFROMWKB", 0, 0, 0, ""}, + { "POLYGONFROMTEXT", 0, 0, 0, ""}, + { "POLYGONFROMWKB", 0, 0, 0, ""}, + { "POSITION", 0, 0, 0, ""}, + { "POW", 0, 0, 0, ""}, + { "POWER", 0, 0, 0, ""}, + { "QUOTE", 0, 0, 0, ""}, + { "RADIANS", 0, 0, 0, ""}, + { "RAND", 0, 0, 0, ""}, + { "RELEASE_LOCK", 0, 0, 0, ""}, + { "REVERSE", 0, 0, 0, ""}, + { "ROUND", 0, 0, 0, ""}, + { "ROW_COUNT", 0, 0, 0, ""}, + { "RPAD", 0, 0, 0, ""}, + { "RTRIM", 0, 0, 0, ""}, + { "SEC_TO_TIME", 0, 0, 0, ""}, + { "SESSION_USER", 0, 0, 0, ""}, + { "SUBDATE", 0, 0, 0, ""}, + { "SIGN", 0, 0, 0, ""}, + { "SIN", 0, 0, 0, ""}, + { "SHA", 0, 0, 0, ""}, + { "SHA1", 0, 0, 0, ""}, + { "SLEEP", 0, 0, 0, ""}, + { "SOUNDEX", 0, 0, 0, ""}, + { "SPACE", 0, 0, 0, ""}, + { "SQRT", 0, 0, 0, ""}, + { "SRID", 0, 0, 0, ""}, + { "STARTPOINT", 0, 0, 0, ""}, + { "STD", 0, 0, 0, ""}, + { "STDDEV", 0, 0, 0, ""}, + { "STDDEV_POP", 0, 0, 0, ""}, + { "STDDEV_SAMP", 0, 0, 0, ""}, + { "STR_TO_DATE", 0, 0, 0, ""}, + { "STRCMP", 0, 0, 0, ""}, + { "SUBSTR", 0, 0, 0, ""}, + { "SUBSTRING", 0, 0, 0, ""}, + { "SUBSTRING_INDEX", 0, 0, 0, ""}, + { "SUBTIME", 0, 0, 0, ""}, + { "SUM", 0, 0, 0, ""}, + { "SYSDATE", 0, 0, 0, ""}, + { "SYSTEM_USER", 0, 0, 0, ""}, + { "TAN", 0, 0, 0, ""}, + { "TIME_FORMAT", 0, 0, 0, ""}, + { "TIME_TO_SEC", 0, 0, 0, ""}, + { "TIMEDIFF", 0, 0, 0, ""}, + { "TO_DAYS", 0, 0, 0, ""}, + { "TOUCHES", 0, 0, 0, ""}, + { "TRIM", 0, 0, 0, ""}, + { "UCASE", 0, 0, 0, ""}, + { "UNCOMPRESS", 0, 0, 0, ""}, + { "UNCOMPRESSED_LENGTH", 0, 0, 0, ""}, + { "UNHEX", 0, 0, 0, ""}, + { "UNIQUE_USERS", 0, 0, 0, ""}, + { "UNIX_TIMESTAMP", 0, 0, 0, ""}, + { "UPPER", 0, 0, 0, ""}, + { "UUID", 0, 0, 0, ""}, + { "VARIANCE", 0, 0, 0, ""}, + { "VAR_POP", 0, 0, 0, ""}, + { "VAR_SAMP", 0, 0, 0, ""}, + { "VERSION", 0, 0, 0, ""}, + { "WEEKDAY", 0, 0, 0, ""}, + { "WEEKOFYEAR", 0, 0, 0, ""}, + { "WITHIN", 0, 0, 0, ""}, + { "X", 0, 0, 0, ""}, + { "Y", 0, 0, 0, ""}, + { "YEARWEEK", 0, 0, 0, ""}, + /* end sentinel */ + { (char *)NULL, 0, 0, 0, ""} +}; + +static const char *load_default_groups[]= +{ "mysql", "mariadb-client", "client", "client-server", "client-mariadb", 0 }; + +static int embedded_server_arg_count= 0; +static char *embedded_server_args[MAX_SERVER_ARGS]; +static const char *embedded_server_groups[]= +{ "server", "embedded", "mysql_SERVER", "mariadb_SERVER", 0 }; + +#ifdef HAVE_READLINE +static int not_in_history(const char *line); +static void initialize_readline (); +static void fix_history(String *final_command); +#endif + +static COMMANDS *find_command(char *name); +static COMMANDS *find_command(char cmd_name); +static bool add_line(String &, char *, size_t line_length, char *, bool *, bool); +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(void); +static void end_timer(ulonglong start_time, char *buff); +static void nice_time(double sec,char *buff,bool part_second); +extern "C" sig_handler mysql_end(int sig) __attribute__ ((noreturn)); +extern "C" sig_handler handle_sigint(int sig); +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) +static sig_handler window_resize(int sig); +#endif + + +const char DELIMITER_NAME[]= "delimiter"; +const uint DELIMITER_NAME_LEN= sizeof(DELIMITER_NAME) - 1; +inline bool is_delimiter_command(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_charset_latin1.strnncoll(name, DELIMITER_NAME_LEN, + 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; i++) + if (commands[i].cmd_char == cmd_char) + return i; + return -1; +} + +static int delimiter_index= -1; +static int charset_index= -1; +static bool real_binary_mode= FALSE; + + +int main(int argc,char *argv[]) +{ + char buff[80]; + + MY_INIT(argv[0]); + DBUG_ENTER("main"); + 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") : + "\\N [\\d]> ",MYF(MY_WME)); + current_prompt = my_strdup(PSI_NOT_INSTRUMENTED, default_prompt,MYF(MY_WME)); + prompt_counter=0; + aborted= 0; + sf_leaking_memory= 1; /* no memory leak reports yet */ + + outfile[0]=0; // no (default) outfile + strmov(pager, "stdout"); // the default, if --pager wasn't given + + { + char *tmp=getenv("PAGER"); + if (tmp && strlen(tmp)) + { + default_pager_set= 1; + strmov(default_pager, tmp); + } + } + if (!isatty(0) || !isatty(1)) + { + status.batch=1; opt_silent=1; + ignore_errors=0; + } + else + status.add_to_history=1; + 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); + else + close(stdout_fileno_copy); /* Clean up dup(). */ + } + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv=argv; + if ((status.exit_status= get_options(argc, (char **) argv))) + { + free_defaults(defaults_argv); + my_end(0); + exit(status.exit_status); + } + + 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); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + if (mysql_server_init(embedded_server_arg_count, embedded_server_args, + (char**) embedded_server_groups)) + { + put_error(NULL); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + sf_leaking_memory= 0; + glob_buffer.realloc(512); + completion_hash_init(&ht, 128); + init_alloc_root(PSI_NOT_INSTRUMENTED, &hash_mem_root, 16384, 0, MYF(0)); + if (sql_connect(current_host,current_db,current_user,opt_password, + opt_silent)) + { + quick= 1; // Avoid history + status.exit_status= 1; + mysql_end(-1); + } + if (!status.batch) + ignore_errors=1; // Don't abort monitor + + if (opt_sigint_ignore) + signal(SIGINT, SIG_IGN); + else + signal(SIGINT, handle_sigint); // Catch SIGINT to clean up + signal(SIGQUIT, mysql_end); // Catch SIGQUIT to clean up + +#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 + + if (!status.batch) + { + put_info("Welcome to the MariaDB monitor. Commands end with ; or \\g.", + INFO_INFO); + my_snprintf((char*) glob_buffer.ptr(), glob_buffer.alloced_length(), + "Your %s connection id is %lu\nServer version: %s\n", + mysql_get_server_name(&mysql), + mysql_thread_id(&mysql), server_version_string(&mysql)); + put_info((char*) glob_buffer.ptr(),INFO_INFO); + put_info(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000"), INFO_INFO); + } + +#ifdef HAVE_READLINE + initialize_readline(); + if (!status.batch && !quick && !opt_html && !opt_xml) + { + /* 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, + strlen(getenv("HOME")) + 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", 10) == 0) + { + /* The .mysql_history file is a symlink to /dev/null, don't use it */ + my_free(histfile); + histfile= 0; + } + } + + /* We used to suggest setting MYSQL_HISTFILE=/dev/null. */ + if (histfile && strncmp(histfile, "/dev/null", 10) == 0) + histfile= NULL; + + if (histfile && histfile[0]) + { + if (verbose) + tee_fprintf(stdout, "Reading history-file %s\n",histfile); + read_history(histfile); + if (!(histfile_tmp= (char*) my_malloc(PSI_NOT_INSTRUMENTED, + strlen(histfile) + 5, MYF(MY_WME)))) + { + fprintf(stderr, "Couldn't allocate memory for temp histfile!\n"); + exit(1); + } + 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); + status.exit_status= read_and_execute(!status.batch); + if (opt_outfile) + end_tee(); + mysql_end(0); +#ifndef _lint + DBUG_RETURN(0); // Keep compiler happy +#endif +} + +sig_handler mysql_end(int sig) +{ +#ifndef _WIN32 + /* + Ignoring SIGQUIT and SIGINT 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); +#endif + + mysql_close(&mysql); +#ifdef HAVE_READLINE + if (!status.batch && !quick && !opt_html && !opt_xml && + 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); + free_root(&hash_mem_root,MYF(0)); + +#endif + if (sig >= 0) + put_info(sig ? "Aborted" : "Bye", INFO_RESULT); + glob_buffer.free(); + old_buffer.free(); + processed_prompt.free(); + my_free(server_version); + my_free(opt_password); + my_free(opt_mysql_unix_port); + my_free(histfile); + my_free(histfile_tmp); + my_free(current_db); + my_free(current_host); + my_free(current_user); + my_free(full_username); + my_free(part_username); + my_free(default_prompt); + my_free(current_prompt); + while (embedded_server_arg_count > 1) + my_free(embedded_server_args[--embedded_server_arg_count]); + mysql_server_end(); + free_defaults(defaults_argv); + my_end(my_end_arg); + exit(status.exit_status); +} + +#ifdef _WIN32 +#define CNV_BUFSIZE 1024 + +/** + Convert user,database,and password to requested charset. + + This is done in the single case when user connects with non-UTF8 + default-character-set, on UTF8 capable Windows. + + User, password, and database are UTF8 encoded, prior to the function, + this needs to be fixed, in case they contain non-ASCIIs. + + Mostly a workaround, to allow existng users with non-ASCII password + to survive upgrade without losing connectivity. +*/ +static void maybe_convert_charset(const char **user, const char **password, + const char **database, const char *csname) +{ + if (GetACP() != CP_UTF8 || !strncmp(csname, "utf8", 4)) + return; + static char bufs[3][CNV_BUFSIZE]; + const char **from[]= {user, password, database}; + CHARSET_INFO *cs= get_charset_by_csname(csname, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME)); + if (!cs) + return; + for (int i= 0; i < 3; i++) + { + const char *str= *from[i]; + if (!str) + continue; + uint errors; + uint len= my_convert(bufs[i], CNV_BUFSIZE, cs, str, (uint32) strlen(str), + &my_charset_utf8mb4_bin, &errors); + bufs[i][len]= 0; + *from[i]= bufs[i]; + } +} +#endif + +/* + set connection-specific options and call mysql_real_connect +*/ +static bool do_connect(MYSQL *mysql, const char *host, const char *user, + const char *password, const char *database, ulong flags) +{ + if (opt_secure_auth) + mysql_options(mysql, MYSQL_SECURE_AUTH, (char *) &opt_secure_auth); +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (opt_use_ssl && opt_protocol <= MYSQL_PROTOCOL_SOCKET) + { + mysql_ssl_set(mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysql"); +#ifdef _WIN32 + maybe_convert_charset(&user, &password, &database,default_charset); +#endif + + return mysql_real_connect(mysql, host, user, password, database, + opt_mysql_port, opt_mysql_unix_port, flags); +} + + +/* + This function handles sigint calls + If query is in process, kill query + If 'source' is executed, abort source command + no query in process, terminate like previous behavior + */ + +sig_handler handle_sigint(int sig) +{ + char kill_buffer[40]; + MYSQL *kill_mysql= NULL; + + /* terminate if no query being executed, or we already tried interrupting */ + if (!executing_query || (interrupted_query == 2)) + { + tee_fprintf(stdout, "Ctrl-C -- exit!\n"); + goto err; + } + + kill_mysql= mysql_init(kill_mysql); + if (!do_connect(kill_mysql,current_host, current_user, opt_password, "", 0)) + { + tee_fprintf(stdout, "Ctrl-C -- sorry, cannot connect to server to kill query, giving up ...\n"); + goto err; + } + + /* First time try to kill the query, second time the connection */ + interrupted_query++; + + /* mysqld < 5 does not understand KILL QUERY, skip to KILL CONNECTION */ + if ((interrupted_query == 1) && (mysql_get_server_version(&mysql) < 50000)) + interrupted_query= 2; + + /* kill_buffer is always big enough because max length of %lu is 15 */ + sprintf(kill_buffer, "KILL %s%lu", + (interrupted_query == 1) ? "QUERY " : "", + mysql_thread_id(&mysql)); + if (verbose) + tee_fprintf(stdout, "Ctrl-C -- sending \"%s\" to server ...\n", + kill_buffer); + mysql_real_query(kill_mysql, kill_buffer, (uint) strlen(kill_buffer)); + mysql_close(kill_mysql); + tee_fprintf(stdout, "Ctrl-C -- query killed. Continuing normally.\n"); + if (in_com_source) + aborted= 1; // Abort source command + return; + +err: +#ifdef _WIN32 + /* + When SIGINT 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 sigint + handler called mysql_end(). + */ + mysql_thread_end(); +#else + mysql_end(sig); +#endif +} + + +#if defined(HAVE_TERMIOS_H) && defined(GWINSZ_IN_SYS_IOCTL) +sig_handler window_resize(int sig) +{ + struct winsize window_size; + + if (ioctl(fileno(stdin), TIOCGWINSZ, &window_size) == 0) + if (window_size.ws_col > 0) + terminal_width= window_size.ws_col; +} +#endif + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"help", 'I', "Synonym for -?", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"abort-source-on-error", OPT_ABORT_SOURCE_ON_ERROR, + "Abort 'source filename' operations in case of errors", + &batch_abort_on_error, &batch_abort_on_error, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, + 0, 0}, + {"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.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_BOOL, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"batch", 'B', + "Don't use history file. Disable interactive behavior. (Enables --silent.)", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"binary-as-hex", 0, "Print binary data as hex", &opt_binhex, &opt_binhex, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", &charsets_dir, + &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"column-type-info", OPT_COLUMN_TYPES, "Display column type information.", + &column_types_flag, &column_types_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"comments", 'c', "Preserve comments. Send comments to the server." + " The default is --skip-comments (discard comments), enable with --comments.", + &preserve_comments, &preserve_comments, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log.", &default_dbug_option, + &default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag, + &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'D', "Database to use.", ¤t_db, + ¤t_db, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"delimiter", OPT_DELIMITER, "Delimiter to be used.", &delimiter_str, + &delimiter_str, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"execute", 'e', "Execute command and quit. (Disables --force and history file.)", 0, + 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"enable-cleartext-plugin", OPT_COMPATIBILTY_CLEARTEXT_PLUGIN, "Obsolete option. Exists only for MySQL compatibility.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"vertical", 'E', "Print the output of a query (rows) vertically.", + &vertical, &vertical, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"force", 'f', "Continue even if we get an SQL error. Sets abort-source-on-error to 0", + &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"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, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"ignore-spaces", 'i', "Ignore space after function names.", + &ignore_spaces, &ignore_spaces, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"init-command", OPT_INIT_COMMAND, + "SQL Command to execute when connecting to MariaDB server. Will " + "automatically be re-executed when reconnecting.", + &opt_init_command, &opt_init_command, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"local-infile", OPT_LOCAL_INFILE, "Enable/disable LOAD DATA LOCAL INFILE.", + &opt_local_infile, &opt_local_infile, 0, GET_BOOL, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"no-beep", 'b', "Turn off beep on error.", &opt_nobeep, + &opt_nobeep, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", ¤t_host, + ¤t_host, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"html", 'H', "Produce HTML output.", &opt_html, &opt_html, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"xml", 'X', "Produce XML output.", &opt_xml, &opt_xml, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"line-numbers", OPT_LINE_NUMBERS, "Write line numbers for errors.", + &line_numbers, &line_numbers, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"skip-line-numbers", 'L', "Don't write line number for errors.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"unbuffered", 'n', "Flush buffer after each query.", &unbuffered, + &unbuffered, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"column-names", OPT_COLUMN_NAMES, "Write column names in results.", + &column_names, &column_names, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"skip-column-names", 'N', + "Don't write column names in results.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"sigint-ignore", OPT_SIGINT_IGNORE, "Ignore SIGINT (CTRL-C).", + &opt_sigint_ignore, &opt_sigint_ignore, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"one-database", 'o', + "Ignore statements except those that occur while the default " + "database is the one named at the command line.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#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.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"password", 'p', + "Password to use when connecting to server. If password is not given it's asked from the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#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, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"progress-reports", OPT_REPORT_PROGRESS, + "Get progress reports for long running commands (like ALTER TABLE)", + &opt_progress_reports, &opt_progress_reports, 0, GET_BOOL, NO_ARG, 1, 0, + 0, 0, 0, 0}, + {"prompt", OPT_PROMPT, "Set the command line prompt to this value.", + ¤t_prompt, ¤t_prompt, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe).", + &opt_protocol_type, &opt_protocol_type, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"raw", 'r', "Write fields without conversion. Used with --batch.", + &opt_raw_data, &opt_raw_data, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"reconnect", OPT_RECONNECT, "Reconnect if the connection is lost. Disable " + "with --disable-reconnect. This option is enabled by default.", + &opt_reconnect, &opt_reconnect, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"silent", 's', "Be more silent. Print results with a tab as separator, " + "each row on new line.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include "sslopt-longopts.h" + {"table", 't', "Output in table format.", &output_tables, + &output_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"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.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", ¤t_user, + ¤t_user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"safe-updates", 'U', "Only allow UPDATE and DELETE that uses keys.", + &safe_updates, &safe_updates, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"i-am-a-dummy", 'U', "Synonym for option --safe-updates, -U.", + &safe_updates, &safe_updates, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"verbose", 'v', "Write more. (-v -v -v gives the table output format).", 0, + 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"wait", 'w', "Wait and retry if connection is down.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"connect_timeout", OPT_CONNECT_TIMEOUT, + "Number of seconds before connection timeout.", + &opt_connect_timeout, &opt_connect_timeout, 0, GET_ULONG, REQUIRED_ARG, + 0, 0, 3600*12, 0, 0, 0}, + {"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, 0, + GET_ULONG, REQUIRED_ARG, 16 *1024L*1024L, 4096, + (longlong) 2*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0}, + {"net_buffer_length", OPT_NET_BUFFER_LENGTH, + "The buffer size for TCP/IP and socket communication.", + &opt_net_buffer_length, &opt_net_buffer_length, 0, GET_ULONG, + REQUIRED_ARG, 16384, 1024, 512*1024*1024L, MALLOC_OVERHEAD, 1024, 0}, + {"select_limit", OPT_SELECT_LIMIT, + "Automatic limit for SELECT when using --safe-updates.", + &select_limit, &select_limit, 0, GET_ULONG, REQUIRED_ARG, 1000L, + 1, ULONG_MAX, 0, 1, 0}, + {"max_join_size", OPT_MAX_JOIN_SIZE, + "Automatic limit for rows in a join when using --safe-updates.", + &max_join_size, &max_join_size, 0, GET_ULONG, REQUIRED_ARG, 1000000L, + 1, ULONG_MAX, 0, 1, 0}, + {"secure-auth", OPT_SECURE_AUTH, "Refuse client connecting to server if it" + " uses old (pre-4.1.1) protocol.", &opt_secure_auth, + &opt_secure_auth, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"server-arg", OPT_SERVER_ARG, "Send embedded server this as a parameter.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"show-warnings", OPT_SHOW_WARNINGS, "Show warnings after every statement.", + &show_warnings, &show_warnings, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"binary-mode", 0, + "Binary mode allows certain character sequences to be processed as data " + "that would otherwise be treated with a special meaning by the parser. " + "Specifically, this switch turns off parsing of all client commands except " + "\\C and DELIMITER in non-interactive mode (i.e., when binary mode is " + "combined with either 1) piped input, 2) the --batch mysql option, or 3) " + "the 'source' command). Also, in binary mode, occurrences of '\\r\\n' and " + "ASCII '\\0' are preserved within strings, whereas by default, '\\r\\n' is " + "translated to '\\n' and '\\0' is disallowed in user input.", + &opt_binary_mode, &opt_binary_mode, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"connect-expired-password", 0, + "Notify the server that this client is prepared to handle expired " + "password sandbox mode even if --batch was specified.", + &opt_connect_expired_password, &opt_connect_expired_password, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void usage(int version) +{ +#ifdef HAVE_READLINE +#if defined(USE_LIBEDIT_INTERFACE) + const char* readline= ""; +#else + const char* readline= "readline"; +#endif + printf("%s Ver %s Distrib %s, for %s (%s) using %s %s\n", + my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE, + readline, rl_library_version); +#else + printf("%s Ver %s Distrib %s, for %s (%s), source revision %s\n", my_progname, VER, + MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE,SOURCE_REVISION); +#endif + + if (version) + return; + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("Usage: %s [OPTIONS] [database]\n", my_progname); + print_defaults("my", load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + switch(opt->id) { + case OPT_CHARSETS_DIR: + strmake_buf(mysql_charsets_dir, argument); + charsets_dir = mysql_charsets_dir; + break; + case OPT_DELIMITER: + if (argument == disabled_my_option) + { + strmov(delimiter, DEFAULT_DELIMITER); + } + else + { + /* Check that delimiter does not contain a backslash */ + if (!strstr(argument, "\\")) + { + strmake_buf(delimiter, argument); + } + else + { + put_info("DELIMITER cannot contain a backslash character", INFO_ERROR); + return 0; + } + } + delimiter_length= (uint)strlen(delimiter); + delimiter_str= delimiter; + break; + case OPT_LOCAL_INFILE: + using_opt_local_infile=1; + 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= 1; + else + { + opt_nopager= 0; + if (argument && strlen(argument)) + { + default_pager_set= 1; + strmake_buf(pager, argument); + strmov(default_pager, pager); + } + else if (default_pager_set) + strmov(pager, default_pager); + else + opt_nopager= 1; + } + break; + case OPT_MYSQL_PROTOCOL: +#ifndef EMBEDDED_LIBRARY + if (!argument[0]) + opt_protocol= 0; + else if ((opt_protocol= + find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + exit(1); +#endif + break; + case OPT_SERVER_ARG: +#ifdef EMBEDDED_LIBRARY + /* + When the embedded server is being tested, the client needs to be + able to pass command-line arguments to the embedded server so it can + locate the language files and data directory. + */ + if (!embedded_server_arg_count) + { + embedded_server_arg_count= 1; + embedded_server_args[0]= (char*) ""; + } + if (embedded_server_arg_count == MAX_SERVER_ARGS-1 || + !(embedded_server_args[embedded_server_arg_count++]= + my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)))) + { + put_info("Can't use server argument", INFO_ERROR); + return 0; + } +#else /*EMBEDDED_LIBRARY */ + printf("WARNING: --server-arg option not supported in this configuration.\n"); +#endif + break; + case OPT_COMPATIBILTY_CLEARTEXT_PLUGIN: + /* + This option exists in MySQL client but not in MariaDB. Users switching from + MySQL might still have this option in their commands, and it will not work + in MariaDB unless it is handled. Therefore output a warning and continue. + */ + printf("WARNING: option '--enable-cleartext-plugin' is obsolete.\n"); + break; + case 'A': + opt_rehash= 0; + break; + case 'N': + column_names= 0; + break; + case 'e': + status.batch= 1; + status.add_to_history= 0; + if (!status.line_buff) + ignore_errors= 0; // do it for the first -e only + if (!(status.line_buff= batch_readline_command(status.line_buff, + (char*) argument))) + return 1; + break; + case 'o': + if (argument == disabled_my_option) + one_database= 0; + else + one_database= skip_updates= 1; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; // Don't require password + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*)argument++= 'x'; // Destroy argument + if (*start) + start[1]=0 ; + tty_password= 0; + } + else + tty_password= 1; + break; + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + debug_info_flag= 1; + 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= 1; + status.add_to_history= 0; + set_if_bigger(opt_silent,1); // more silent + break; + case 'W': +#ifdef _WIN32 + opt_protocol = MYSQL_PROTOCOL_PIPE; + opt_protocol_type= "pipe"; +#endif + break; +#include + case 'f': + batch_abort_on_error= 0; + break; + case 'V': + usage(1); + status.exit_status= 0; + mysql_end(-1); + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + case 'I': + case '?': + usage(0); + status.exit_status= 0; + mysql_end(-1); + } + return 0; +} + + +static int get_options(int argc, char **argv) +{ + char *tmp, *pagpoint; + int ho_error; + MYSQL_PARAMETERS *mysql_params= mysql_get_parameters(); + + tmp= (char *) getenv("MYSQL_HOST"); + if (tmp) + current_host= my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME)); + + pagpoint= getenv("PAGER"); + if (!((char*) (pagpoint))) + { + strmov(pager, "stdout"); + opt_nopager= 1; + } + else + strmov(pager, pagpoint); + strmov(default_pager, pager); + + opt_max_allowed_packet= *mysql_params->p_max_allowed_packet; + opt_net_buffer_length= *mysql_params->p_net_buffer_length; + + if ((ho_error=handle_options(&argc, &argv, my_long_options, get_one_option))) + return(ho_error); + + *mysql_params->p_max_allowed_packet= opt_max_allowed_packet; + *mysql_params->p_net_buffer_length= opt_net_buffer_length; + + if (status.batch) /* disable pager and outfile in this case */ + { + strmov(default_pager, "stdout"); + strmov(pager, "stdout"); + opt_nopager= 1; + default_pager_set= 0; + opt_outfile= 0; + opt_reconnect= 0; + connect_flag= 0; /* Not in interactive mode */ + opt_progress_reports= 0; + } + + if (argc > 1) + { + usage(0); + exit(1); + } + if (argc == 1) + { + skip_updates= 0; + my_free(current_db); + current_db= my_strdup(PSI_NOT_INSTRUMENTED, *argv, MYF(MY_WME)); + } + if (tty_password) + opt_password= my_get_tty_password(NullS); + 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; + + if (opt_progress_reports) + connect_flag|= CLIENT_PROGRESS_OBSOLETE; + + return(0); +} + +static int read_and_execute(bool interactive) +{ + char *line= NULL; + char in_string=0; + ulong line_number=0; + bool ml_comment= 0; + COMMANDS *com; + size_t line_length= 0; + status.exit_status=1; + + real_binary_mode= !interactive && opt_binary_mode; + while (!aborted) + { + 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, real_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 (!real_binary_mode && strlen(line) != line_length) + { + status.exit_status= 1; + String msg; + msg.append(STRING_WITH_LEN( + "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, strlen(line)); + msg.append(STRING_WITH_LEN("'.")); + 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 + { + char *prompt= (char*) (ml_comment ? " /*> " : + glob_buffer.is_empty() ? construct_prompt() : + !in_string ? " -> " : + in_string == '\'' ? + " '> " : (in_string == '`' ? + " `> " : + " \"> ")); + if (opt_outfile && glob_buffer.is_empty()) + fflush(OUTFILE); + +#if defined(_WIN32) + tee_fputs(prompt, stdout); + line= win_readline(); +#else + if (opt_outfile) + fputs(prompt, OUTFILE); + /* + free the previous entered line. + Note: my_free() cannot be used here as the memory was allocated under + the readline/libedit library. + */ + if (line) + free(line); + line= readline(prompt); +#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; + } + + /* + Check if line is a mysql command line + (We want to allow help, print and clear anywhere at line start + */ + if ((named_cmds || glob_buffer.is_empty()) + && !ml_comment && !in_string && (com= find_command(line))) + { + if ((*com->func)(&glob_buffer,line) > 0) + break; + if (glob_buffer.is_empty()) // If buffer was emptied + in_string=0; +#ifdef HAVE_READLINE + if (interactive && status.add_to_history && not_in_history(line)) + add_history(line); +#endif + continue; + } + if (add_line(glob_buffer, line, line_length, &in_string, &ml_comment, + status.line_buff ? status.line_buff->truncated : 0)) + 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; + if (com_go(&glob_buffer,line) <= 0) + status.exit_status=0; + } + } + +#if !defined(_WIN32) + if (interactive) + /* + free the last entered line. + Note: my_free() cannot be used here as the memory was allocated under + the readline/libedit library. + */ + free(line); +#endif + + /* + If the function is called by 'source' command, it will return to interactive + mode, so real_binary_mode should be FALSE. Otherwise, it will exit the + program, it is safe to set real_binary_mode to FALSE. + */ + real_binary_mode= FALSE; + + return status.exit_status; +} + + +/** + 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_ENTER("find_command"); + DBUG_PRINT("enter", ("cmd_char: %d", cmd_char)); + + int index= -1; + + /* + In binary-mode, we disallow all mysql commands except '\C' + and DELIMITER. + */ + if (real_binary_mode) + { + 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)); + DBUG_RETURN(&commands[index]); + } + else + DBUG_RETURN((COMMANDS *) 0); +} + +/** + 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_ENTER("find_command"); + + DBUG_ASSERT(name != NULL); + 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 ((!real_binary_mode && strstr(name, "\\g")) || + (strstr(name, delimiter) && + !is_delimiter_command(name, DELIMITER_NAME_LEN))) + DBUG_RETURN((COMMANDS *) 0); + + if ((end=strcont(name, " \t"))) + { + len=(uint) (end - name); + while (my_isspace(charset_info, *end)) + end++; + if (!*end) + end= 0; // no arguments to function + } + else + len= (uint) strlen(name); + + int index= -1; + if (real_binary_mode) + { + 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_charset_latin1.strnncoll((uchar*) name, len, + (uchar*) commands[i].name, len) && + (commands[i].name[len] == '\0') && + (!end || (commands[i].takes_params && get_arg(name, CHECK)))) + { + index= i; + break; + } + } + } + + if (index >= 0) + { + DBUG_PRINT("exit", ("found command: %s", commands[index].name)); + DBUG_RETURN(&commands[index]); + } + DBUG_RETURN((COMMANDS *) 0); +} + + +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= 0; + bool ss_comment= 0; + DBUG_ENTER("add_line"); + + if (!line[0] && buffer.is_empty()) + DBUG_RETURN(0); +#ifdef HAVE_READLINE + if (status.add_to_history && line[0] && not_in_history(line)) + add_history(line); +#endif + char *end_of_line= line + line_length; + + 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; + } + +#ifdef USE_MB + // Accept multi-byte characters as-is + int length; + if (charset_info->use_mb() && + (length= my_ismbchar(charset_info, pos, end_of_line))) + { + if (!*ml_comment || preserve_comments) + { + while (length--) + *out++ = *pos++; + pos--; + } + else + pos+= length - 1; + continue; + } +#endif + if (!*ml_comment && inchar == '\\' && *in_string != '`' && + !(*in_string == '"' && + (mysql.server_status & SERVER_STATUS_ANSI_QUOTES)) && + !(*in_string && + (mysql.server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES))) + { + // Found possible one character command like \c + + /* + The null-terminating character (ASCII '\0') marks the end of user + input. Then, by default, upon encountering a '\0' while parsing, it + should stop. However, some data naturally contains binary zeros + (e.g., zipped files). Real_binary_mode signals the parser to expect + '\0' within the data and not to end parsing if found. + */ + if (!(inchar = (uchar) *++pos) && (!real_binary_mode || !*in_string)) + break; // readline adds one '\' + if (*in_string || inchar == 'N') // \N is short for NULL + { // Don't allow commands in string + *out++='\\'; + *out++= (char) inchar; + continue; + } + if ((com= find_command((char) inchar))) + { + // Flush previously accepted characters + if (out != line) + { + buffer.append(line, (uint) (out-line)); + out= line; + } + + if ((*com->func)(&buffer,pos-1) > 0) + DBUG_RETURN(1); // 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) + DBUG_RETURN(1); + *out++='\\'; + *out++=(char) inchar; + continue; + } + } + else if (!*ml_comment && !*in_string && 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 + size_t length= strlen(pos); + buffer.append(pos, length); + pos+= length; + } + + pos--; + + if ((com= find_command(buffer.c_ptr()))) + { + + if ((*com->func)(&buffer, buffer.c_ptr()) > 0) + DBUG_RETURN(1); // Quit + } + else + { + if (com_go(&buffer, 0) > 0) // < 0 is not fatal + DBUG_RETURN(1); + } + buffer.length(0); + } + else if (!*ml_comment && + (!*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. + We also ignore lines starting with '--', even if there + isn't a whitespace after. (This makes it easier to run + mysql-test-run cases through the client) + */ + ((my_isspace(charset_info,pos[2]) || !pos[2]) || + (buffer.is_empty() && out == line)))))) + { + // Flush previously accepted characters + if (out != line) + { + buffer.append(line, (uint32) (out - line)); + out= line; + } + + // comment to end of line + if (preserve_comments) + { + bool started_with_nothing= !buffer.length(); + + buffer.append(pos, strlen(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, 0) > 0) // < 0 is not fatal + DBUG_RETURN(1); + buffer.length(0); + } + } + + break; + } + else if (!*in_string && inchar == '/' && *(pos+1) == '*' && + !(*(pos+2) == '!' || (*(pos+2) == 'M' && *(pos+3) == '!'))) + { + if (preserve_comments) + { + *out++= *pos++; // copy '/' + *out++= *pos; // copy '*' + } + else + pos++; + *ml_comment= 1; + 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= 0; + 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= 1; + } + else + { // Add found char to buffer + if (!*in_string && inchar == '/' && *(pos + 1) == '*' && + *(pos + 2) == '!') + ss_comment= 1; + else if (!*in_string && ss_comment && inchar == '*' && *(pos + 1) == '/') + ss_comment= 0; + if (inchar == *in_string) + *in_string= 0; + else if (!*ml_comment && !*in_string && + (inchar == '\'' || inchar == '"' || inchar == '`')) + *in_string= (char) inchar; + if (!*ml_comment || preserve_comments) + { + if (need_space && !my_isspace(charset_info, (char)inchar)) + *out++= ' '; + need_space= 0; + *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 + ";DELIMITER ") : 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.realloc(buffer.length()+length+IO_SIZE); + if ((!*ml_comment || preserve_comments) && buffer.append(line, length)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + +/***************************************************************** + Interface to Readline Completion +******************************************************************/ + +#ifdef HAVE_READLINE + +C_MODE_START +static char *new_command_generator(const char *text, int); +static char **new_mysql_completion(const char *text, int start, int end); +C_MODE_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(USE_NEW_READLINE_INTERFACE) +static int fake_magic_space(int, int); +extern "C" char *no_completion(const char*,int) +#elif defined(USE_LIBEDIT_INTERFACE) +static int fake_magic_space(const char *, int); +extern "C" int no_completion(const char*,int) +#else +extern "C" char *no_completion() +#endif +{ + return 0; /* No filename completion */ +} + +/* glues pieces of history back together if in pieces */ +static void fix_history(String *final_command) +{ + int total_lines = 1; + char *ptr = final_command->c_ptr(); + String fixed_buffer; /* Converted buffer */ + char str_char = '\0'; /* Character if we are in a string or not */ + + /* 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_history(fixed_buffer.ptr()); +} + +/* + 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 == 0) + return 1; + if (strcmp(oldhist->line,line) == 0) + return 0; + return 1; +} + + +#if defined(USE_NEW_READLINE_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 () +{ + /* Allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name= (char *) "mysql"; + rl_terminal_name= getenv("TERM"); +#ifdef HAVE_SETLOCALE + setlocale(LC_ALL,""); +#endif + + /* Tell the completer that we want a crack first. */ +#if defined(USE_NEW_READLINE_INTERFACE) + rl_attempted_completion_function= (rl_completion_func_t*)&new_mysql_completion; + rl_completion_entry_function= (rl_compentry_func_t*)&no_completion; + + rl_add_defun("magic-space", (rl_command_func_t *)&fake_magic_space, -1); +#elif defined(USE_LIBEDIT_INTERFACE) + rl_attempted_completion_function= (CPPFunction*)&new_mysql_completion; + rl_completion_entry_function= &no_completion; + rl_add_defun("magic-space", (Function*)&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 __attribute__((unused)), + int end __attribute__((unused))) +{ + if (!status.batch && !quick) +#if defined(USE_NEW_READLINE_INTERFACE) + return rl_completion_matches(text, new_command_generator); +#else + return completion_matches((char *)text, (CPFunction *)new_command_generator); +#endif + else + return (char**) 0; +} + +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++ ; ipData; + 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=0,*tables=0; + MYSQL_RES *fields; + static char ***field_names= 0; + 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_ENTER("build_completion_hash"); + + if (status.batch || quick || !current_db) + DBUG_VOID_RETURN; // We don't need completion in batches + if (!rehash) + DBUG_VOID_RETURN; + + /* Free old used memory */ + if (field_names) + field_names=0; + completion_hash_clean(&ht); + free_root(&hash_mem_root,MYF(0)); + + /* hash this file's known subset of SQL commands */ + while (cmd->name) { + add_word(&ht,(char*) cmd->name); + cmd++; + } + + /* hash MySQL functions (to be implemented) */ + + /* hash all database names */ + if (mysql_query(&mysql,"show databases") == 0) + { + if (!(databases = mysql_store_result(&mysql))) + put_info(mysql_error(&mysql),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,"show tables")==0) + { + if (!(tables = mysql_store_result(&mysql))) + put_info(mysql_error(&mysql),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 */ + { + DBUG_VOID_RETURN; + } + mysql_data_seek(tables,0); + if (!(field_names= (char ***) alloc_root(&hash_mem_root,sizeof(char **) * + (uint) (mysql_num_rows(tables)+1)))) + { + mysql_free_result(tables); + DBUG_VOID_RETURN; + } + i=0; + while ((table_row=mysql_fetch_row(tables))) + { + if ((fields=mysql_list_fields(&mysql,(const char*) table_row[0],NullS))) + { + num_fields=mysql_num_fields(fields); + if (!(field_names[i] = (char **) alloc_root(&hash_mem_root, + sizeof(char *) * + (num_fields*2+1)))) + { + mysql_free_result(fields); + break; + } + field_names[i][num_fields*2]= NULL; + 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]= 0; + + i++; + } + mysql_free_result(tables); + field_names[i]=0; // End pointer + DBUG_VOID_RETURN; +} + + /* for gnu readline */ + +#ifndef HAVE_INDEX +extern "C" { +extern char *index(const char *,int c),*rindex(const char *,int); + +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) +{ + reg3 char *t; + + t = NullS; + do if (*s == (char) c) t = (char*) s; while (*s++); + return (char*) t; +} +} +#endif +#endif /* HAVE_READLINE */ + + +static int reconnect(void) +{ + /* purecov: begin tested */ + if (opt_reconnect) + { + put_info("No connection. Trying to reconnect...",INFO_INFO); + (void) com_connect((String *) 0, 0); + if (opt_rehash) + com_rehash(NULL, NULL); + } + if (!connected) + return put_info("Can't connect to the server\n",INFO_ERROR); + my_free(server_version); + server_version= 0; + /* purecov: end */ + return 0; +} + +static void get_current_db() +{ + MYSQL_RES *res; + + /* If one_database is set, current_db is not supposed to change. */ + if (one_database) + return; + + my_free(current_db); + current_db= NULL; + /* In case of error below current_db will be NULL */ + if (!mysql_query(&mysql, "SELECT DATABASE()") && + (res= mysql_use_result(&mysql))) + { + 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); + } +} + +/*************************************************************************** + The different commands +***************************************************************************/ + +int mysql_real_query_for_lazy(const char *buf, size_t length) +{ + for (uint retry=0;; retry++) + { + int error; + if (!mysql_real_query(&mysql,buf,(ulong)length)) + return 0; + error= put_error(&mysql); + if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR || retry > 1 || + !opt_reconnect) + return error; + if (reconnect()) + return error; + } +} + +int mysql_store_result_for_lazy(MYSQL_RES **result) +{ + if ((*result=mysql_store_result(&mysql))) + return 0; + + if (mysql_error(&mysql)[0]) + return put_error(&mysql); + return 0; +} + +static void print_help_item(MYSQL_ROW *cur, int num_name, int num_cat, char *last_char) +{ + 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 __attribute__((unused)), + char *line __attribute__((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= *buffer; + old_buffer.copy(); + } + + 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) + { + unsigned int num_fields= mysql_num_fields(result); + my_ulonglong num_rows= mysql_num_rows(result); + if (num_fields==3 && num_rows==1) + { + if (!(cur= mysql_fetch_row(result))) + { + 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 UNINIT_VAR(num_name), UNINIT_VAR(num_cat); + + 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 ',\nwhere 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 ', where 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 (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 __attribute__((unused)), + char *line __attribute__((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("\nGeneral information about MariaDB can be found at\n" + "http://mariadb.org\n", INFO_INFO); + put_info("List of all client 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= strmov(buff, commands[i].name); + for (j= (int)strlen(commands[i].name); j < 10; j++) + end= strmov(end, " "); + if (commands[i].func) + tee_fprintf(stdout, "%s(\\%c) %s\n", buff, + commands[i].cmd_char, commands[i].doc); + } + if (connected && mysql_get_server_version(&mysql) >= 40100) + put_info("\nFor server side help, type 'help contents'\n", INFO_INFO); + return 0; +} + + + /* ARGSUSED */ +static int +com_clear(String *buffer,char *line __attribute__((unused))) +{ +#ifdef HAVE_READLINE + if (status.add_to_history) + fix_history(buffer); +#endif + buffer->length(0); + return 0; +} + +static void adjust_console_codepage(const char *name __attribute__((unused))) +{ +#ifdef _WIN32 + if (my_set_console_cp(name) < 0) + { + char buf[128]; + snprintf(buf, sizeof(buf), + "WARNING: Could not determine Windows codepage for charset '%s'," + "continue using codepage %u", name, GetConsoleOutputCP()); + put_info(buf, INFO_INFO); + } +#endif +} + + + /* ARGSUSED */ +static int +com_charset(String *buffer __attribute__((unused)), char *line) +{ + char buff[256], *param; + CHARSET_INFO * new_cs; + strmake_buf(buff, line); + param= get_arg(buff, GET); + 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_UTF8_IS_UTF8MB3 | MY_WME)); + if (new_cs) + { + charset_info= new_cs; + mysql_set_character_set(&mysql, charset_info->cs_name.str); + default_charset= (char *)charset_info->cs_name.str; + put_info("Charset changed", INFO_INFO); + adjust_console_codepage(charset_info->cs_name.str); + } + 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(String *buffer,char *line __attribute__((unused))) +{ + char buff[200]; /* about 110 chars used so far */ + char time_buff[53+3+1]; /* time max + space & parens + NUL */ + MYSQL_RES *result; + ulonglong timer; + ulong warnings= 0; + uint error= 0; + int err= 0; + + interrupted_query= 0; + if (!status.batch) + { + old_buffer= *buffer; // Save for edit command + old_buffer.copy(); + } + + /* Remove garbage for nicer messages */ + LINT_INIT_STRUCT(buff[0]); + remove_cntrl(*buffer); + + if (buffer->is_empty()) + { + if (status.batch) // Ignore empty queries. + 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,0); + + if (skip_updates && + (buffer->length() < 4 || charset_info->strnncoll((const uchar*)buffer->ptr(),4, + (const uchar*)"SET ",4))) + { + (void) put_info("Ignoring query to other database",INFO_INFO); + return 0; + } + + timer= microsecond_interval_timer(); + executing_query= 1; + error= mysql_real_query_for_lazy(buffer->ptr(),buffer->length()); + report_progress_end(); + +#ifdef HAVE_READLINE + if (status.add_to_history) + { + const char *delim= vertical ? "\\G" : delimiter; + buffer->append(delim, strlen(delim)); + /* Append final command onto history */ + fix_history(buffer); + } +#endif + + buffer->length(0); + + if (error) + goto end; + + do + { + char *pos; + + if (quick) + { + if (!(result=mysql_use_result(&mysql)) && mysql_field_count(&mysql)) + { + error= put_error(&mysql); + goto end; + } + } + else + { + error= mysql_store_result_for_lazy(&result); + if (error) + goto end; + } + + if (verbose >= 3 || !opt_silent) + 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) + { + strmov(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); + sprintf(buff,"%ld %s in set", + (long) mysql_num_rows(result), + (long) mysql_num_rows(result) == 1 ? "row" : "rows"); + end_pager(); + if (mysql_errno(&mysql)) + error= put_error(&mysql); + } + } + else if (mysql_affected_rows(&mysql) == ~(ulonglong) 0) + strmov(buff,"Query OK"); + else + sprintf(buff,"Query OK, %ld %s affected", + (long) mysql_affected_rows(&mysql), + (long) mysql_affected_rows(&mysql) == 1 ? "row" : "rows"); + + pos=strend(buff); + if ((warnings= mysql_warning_count(&mysql))) + { + *pos++= ','; + *pos++= ' '; + pos=int10_to_str(warnings, pos, 10); + pos=strmov(pos, " warning"); + if (warnings != 1) + *pos++= 's'; + } + strmov(pos, time_buff); + put_info(buff,INFO_RESULT); + if (mysql_info(&mysql)) + put_info(mysql_info(&mysql),INFO_RESULT); + put_info("",INFO_RESULT); // Empty row + + if (result && !mysql_eof(result)) /* Something wrong when using quick */ + error= put_error(&mysql); + else if (unbuffered) + fflush(stdout); + mysql_free_result(result); + } while (!(err= mysql_next_result(&mysql))); + if (err >= 1) + error= put_error(&mysql); + +end: + + /* Show warnings if any or error occurred */ + if (show_warnings == 1 && (warnings >= 1 || error)) + print_warnings(); + + if (!error && !status.batch && + (mysql.server_status & SERVER_STATUS_DB_DROPPED)) + get_current_db(); + + executing_query= 0; + return error; /* New command follows */ +} + + +static void init_pager() +{ +#ifdef USE_POPEN + if (!opt_nopager) + { + if (!(PAGER= popen(pager, "w"))) + { + 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(); + if (!(new_outfile= my_fopen(file_name, O_APPEND | O_WRONLY, MYF(MY_WME)))) + { + tee_fprintf(stdout, "Error logging to file '%s'\n", file_name); + return; + } + OUTFILE = new_outfile; + strmake_buf(outfile, file_name); + tee_fprintf(stdout, "Logging to file '%s'\n", file_name); + opt_outfile= 1; + return; +} + + +static void end_tee() +{ + my_fclose(OUTFILE, MYF(0)); + OUTFILE= 0; + opt_outfile= 0; + return; +} + + +static int +com_ego(String *buffer,char *line) +{ + int result; + bool oldvertical=vertical; + vertical=1; + result=com_go(buffer,line); + vertical=oldvertical; + return result; +} + + +static const char *fieldtype2str(enum enum_field_types type) +{ + switch (type) { + case MYSQL_TYPE_BIT: return "BIT"; + case MYSQL_TYPE_BLOB: return "BLOB"; + case MYSQL_TYPE_DATE: return "DATE"; + case MYSQL_TYPE_DATETIME: return "DATETIME"; + case MYSQL_TYPE_NEWDECIMAL: return "NEWDECIMAL"; + case MYSQL_TYPE_DECIMAL: return "DECIMAL"; + case MYSQL_TYPE_DOUBLE: return "DOUBLE"; + case MYSQL_TYPE_ENUM: return "ENUM"; + case MYSQL_TYPE_FLOAT: return "FLOAT"; + case MYSQL_TYPE_GEOMETRY: return "GEOMETRY"; + case MYSQL_TYPE_INT24: return "INT24"; + case MYSQL_TYPE_LONG: return "LONG"; + case MYSQL_TYPE_LONGLONG: return "LONGLONG"; + case MYSQL_TYPE_LONG_BLOB: return "LONG_BLOB"; + case MYSQL_TYPE_MEDIUM_BLOB: return "MEDIUM_BLOB"; + case MYSQL_TYPE_NEWDATE: return "NEWDATE"; + case MYSQL_TYPE_NULL: return "NULL"; + case MYSQL_TYPE_SET: return "SET"; + case MYSQL_TYPE_SHORT: return "SHORT"; + case MYSQL_TYPE_STRING: return "STRING"; + case MYSQL_TYPE_TIME: return "TIME"; + case MYSQL_TYPE_TIMESTAMP: return "TIMESTAMP"; + case MYSQL_TYPE_TINY: return "TINY"; + case MYSQL_TYPE_TINY_BLOB: return "TINY_BLOB"; + case MYSQL_TYPE_VAR_STRING: return "VAR_STRING"; + case MYSQL_TYPE_YEAR: return "YEAR"; + default: return "?-unknown-?"; + } +} + +static char *fieldflags2str(uint f) { + static char buf[1024]; + char *s=buf; + *s=0; +#define ff2s_check_flag(X) \ + if (f & X ## _FLAG) { s=strmov(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(GROUP); + /* + CONTEXT_COLLATION_FLAG (former BINCMP_FLAG) is used at parse + time only and should never show up on the client side. Don't test it. + */ + 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))) + { + Client_field_metadata metadata(field); + BinaryStringBuffer<128> data_type_metadata_str; + metadata.print_data_type_related_attributes(&data_type_metadata_str); + tee_fprintf(PAGER, "Field %3u: `%s`\n" + "Org_field: `%s`\n" + "Catalog: `%s`\n" + "Database: `%s`\n" + "Table: `%s`\n" + "Org_table: `%s`\n" + "Type: %s%s%.*s%s\n" + "Collation: %s (%u)\n" + "Length: %lu\n" + "Max_length: %lu\n" + "Decimals: %u\n" + "Flags: %s\n\n", + ++i, + field->name, field->org_name, field->catalog, field->db, + field->table, field->org_table, fieldtype2str(field->type), + data_type_metadata_str.length() ? " (" : "", + data_type_metadata_str.length(), data_type_metadata_str.ptr(), + data_type_metadata_str.length() ? ")" : "", + get_charset_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) +{ + if ((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_GEOMETRY)) + return 1; + return 0; +} + + +/* Print binary value as hex literal (0x ...) */ + +static void +print_as_hex(FILE *output_file, const char *str, size_t len, size_t total_bytes_to_send) +{ + const char *ptr= str, *end= ptr+len; + size_t i; + fprintf(output_file, "0x"); + for(; ptr < end; ptr++) + fprintf(output_file, "%02X", *((uchar*)ptr)); + for (i= 2*len+2; i < total_bytes_to_send; i++) + tee_putc((int)' ', output_file); +} + + +static void +print_table_data(MYSQL_RES *result) +{ + String separator(256); + MYSQL_ROW cur; + bool *num_flag; + + num_flag=(bool*) my_alloca(sizeof(bool)*mysql_num_fields(result)); + if (column_types_flag) + { + print_field_types(result); + if (!mysql_num_rows(result)) + { + my_afree((uchar*) num_flag); + return; + } + mysql_field_seek(result,0); + } + separator.copy("+",1,charset_info); + while (MYSQL_FIELD *field= mysql_fetch_field(result)) + { + uint length= column_names ? field->name_length : 0; + if (quick) + length= MY_MAX(length,field->length); + else + length= MY_MAX(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=length; + num_flag[mysql_field_tell(result) - 1]= IS_NUM(field->type); + separator.fill(separator.length()+length+2,'-'); + separator.append('+'); + } + separator.append('\0'); // End marker for \0 + tee_puts((char*) separator.ptr(), PAGER); + if (column_names) + { + mysql_field_seek(result,0); + (void) tee_fputs("|", PAGER); + while (MYSQL_FIELD *field= mysql_fetch_field(result)) + { + size_t name_length= (uint) strlen(field->name); + size_t numcells= charset_info->numcells(field->name, + field->name + name_length); + size_t display_length= field->max_length + name_length - numcells; + tee_fprintf(PAGER, " %-*s |",(int) MY_MIN(display_length, + MAX_COLUMN_LENGTH), + field->name); + } + (void) tee_fputs("\n", PAGER); + tee_puts((char*) separator.ptr(), PAGER); + } + + while ((cur= mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths= mysql_fetch_lengths(result); + (void) tee_fputs("| ", PAGER); + mysql_field_seek(result, 0); + for (uint off= 0; off < mysql_num_fields(result); off++) + { + const char *buffer; + uint data_length; + uint field_max_length; + uint extra_padding; + + if (off) + (void) tee_fputs(" ", PAGER); + + if (cur[off] == NULL) + { + buffer= "NULL"; + data_length= 4; + } + else + { + buffer= cur[off]; + data_length= (uint) lengths[off]; + } + + MYSQL_FIELD *field= mysql_fetch_field(result); + field_max_length= field->max_length; + + /* + 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. + */ + size_t visible_length= charset_info->numcells(buffer, buffer + data_length); + extra_padding= (uint) (data_length - visible_length); + + if (opt_binhex && is_binary_field(field)) + print_as_hex(PAGER, cur[off], lengths[off], field_max_length); + else 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); + } + tee_puts((char*) separator.ptr(), PAGER); + my_afree((uchar*) num_flag); +} + +/** + 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= MY_MAX(length, field->length); + else + length= MY_MAX(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 DBUG_OFF + offset= mysql_field_tell(result); + DBUG_ASSERT(offset == 0); +#else + offset= 0; +#endif + + while ((field= mysql_fetch_field(result)) != NULL) + 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; + const char *p; + + if (right_justified) + for (i= data_length; i < total_bytes_to_send; i++) + tee_putc((int)' ', PAGER); + + for (i= 0, p= data; i < data_length; i+= 1, p+= 1) + { + if (*p == '\0') + tee_putc((int)' ', PAGER); + else + tee_putc((int)*p, PAGER); + } + + 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("", PAGER); + if (column_names) + { + (void) tee_fputs("", PAGER); + while((field = mysql_fetch_field(result))) + { + tee_fputs("", PAGER); + } + (void) tee_fputs("", 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("", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + (void) tee_fputs("", PAGER); + } + (void) tee_fputs("", PAGER); + } + (void) tee_fputs("
", PAGER); + if (field->name && field->name[0]) + xmlencode_print(field->name, field->name_length); + else + tee_fputs(field->name ? "   " : "NULL", PAGER); + tee_fputs("
", 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("
", PAGER); +} + + +static void +print_table_data_xml(MYSQL_RES *result) +{ + MYSQL_ROW cur; + MYSQL_FIELD *fields; + + mysql_field_seek(result,0); + + tee_fputs("\n\n", + 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 \n", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + tee_fprintf(PAGER, "\t"); + 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, "\n"); + } + else + tee_fprintf(PAGER, "\" xsi:nil=\"true\" />\n"); + } + (void) tee_fputs(" \n", PAGER); + } + (void) tee_fputs("\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))) + { + 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]) + { + unsigned int i; + const char *p; + if (opt_binhex && is_binary_field(field)) + fprintf(PAGER, "0x"); + for (i= 0, p= cur[off]; i < lengths[off]; i+= 1, p+= 1) + { + if (opt_binhex && is_binary_field(field)) + fprintf(PAGER, "%02X", *((uchar*)p)); + else + { + if (*p == '\0') + tee_putc((int)' ', PAGER); + else + tee_putc((int)*p, PAGER); + } + } + 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; + my_ulonglong num_rows; + + /* Save current error before calling "show warnings" */ + uint error= mysql_errno(&mysql); + + /* 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], NULL, 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 0; +} + + +static void +xmlencode_print(const char *src, uint length) +{ + if (!src) + tee_fputs("NULL", PAGER); + else + { + for (const char *p = src; length; p++, length--) + { + const char *t; + if ((t = array_value(xmlmeta, *p))) + tee_fputs(t, PAGER); + else + tee_putc(*p, PAGER); + } + } +} + + +static void +safe_put_field(const char *pos,ulong length) +{ + if (!pos) + tee_fputs("NULL", PAGER); + else + { + if (opt_raw_data) + { + unsigned long i; + /* Can't use tee_fputs(), it stops with NUL characters. */ + for (i= 0; i < length; i++, pos++) + tee_putc(*pos, PAGER); + } + else for (const char *end=pos+length ; pos != end ; pos++) + { +#ifdef USE_MB + int l; + if (charset_info->use_mb() && + (l = my_ismbchar(charset_info, pos, end))) + { + while (l--) + tee_putc(*pos++, PAGER); + pos--; + continue; + } +#endif + if (!*pos) + tee_fputs("\\0", PAGER); // This makes everything hard + else if (*pos == '\t') + tee_fputs("\\t", PAGER); // This would destroy tab format + else if (*pos == '\n') + tee_fputs("\\n", PAGER); // This too + else if (*pos == '\\') + tee_fputs("\\\\", PAGER); + else + tee_putc(*pos, PAGER); + } + } +} + + +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 __attribute__((unused)), + char *line __attribute__((unused))) +{ + char file_name[FN_REFLEN], *end, *param; + + if (status.batch) + return 0; + while (my_isspace(charset_info,*line)) + line++; + if (!(param = strchr(line, ' '))) // if outfile wasn't given, use the default + { + if (!strlen(outfile)) + { + printf("No previous outfile available, you must give a filename!\n"); + return 0; + } + else if (opt_outfile) + { + tee_fprintf(stdout, "Currently logging to file '%s'\n", outfile); + return 0; + } + else + param = outfile; //resume using the old outfile + } + + /* eliminate the spaces before the parameters */ + while (my_isspace(charset_info,*param)) + param++; + end= strmake_buf(file_name, param); + /* 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 __attribute__((unused)), + char *line __attribute__((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 __attribute__((unused)), + char *line __attribute__((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=1; + strmov(pager, "stdout"); + PAGER= stdout; + return 0; + } + strmov(pager, default_pager); + } + else + { + end= strmake_buf(pager_name, param); + while (end > pager_name && (my_isspace(charset_info,end[-1]) || + my_iscntrl(charset_info,end[-1]))) + end--; + end[0]=0; + strmov(pager, pager_name); + strmov(default_pager, pager_name); + } + opt_nopager=0; + tee_fprintf(stdout, "PAGER set to '%s'\n", pager); + return 0; +} + + +static int +com_nopager(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + strmov(pager, "stdout"); + opt_nopager=1; + PAGER= stdout; + tee_fprintf(stdout, "PAGER set to stdout\n"); + return 0; +} +#endif + +#ifdef USE_POPEN +static int +com_edit(String *buffer,char *line __attribute__((unused))) +{ + char filename[FN_REFLEN],buff[160]; + int fd,tmp,error; + const char *editor; + MY_STAT stat_arg; + + if ((fd= create_temp_file(filename,NullS,"sql", 0, MYF(MY_WME))) < 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 = IF_WIN("notepad","vi"); + strxmov(buff,editor," ",filename,NullS); + if ((error= system(buff))) + { + char errmsg[100]; + sprintf(errmsg, "Command '%.40s' failed", buff); + put_info(errmsg, INFO_ERROR, 0, NullS); + goto err; + } + + if (!my_stat(filename,&stat_arg,MYF(MY_WME))) + goto err; + if ((fd = my_open(filename,O_RDONLY, MYF(MY_WME))) < 0) + goto err; + (void) buffer->alloc((uint) stat_arg.st_size); + if ((tmp=(int)my_read(fd,(uchar*) buffer->ptr(),buffer->alloced_length(),MYF(0))) >= 0) + 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 __attribute__((unused)), + char *line __attribute__((unused))) +{ + status.exit_status=0; + return 1; +} + +static int +com_rehash(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ +#ifdef HAVE_READLINE + build_completion_hash(1, 0); +#endif + return 0; +} + + +#ifdef USE_POPEN +static int +com_shell(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + char *shell_cmd; + + /* Skip space from line begin */ + while (my_isspace(charset_info, *line)) + line++; + if (!(shell_cmd = strchr(line, ' '))) + { + put_info("Usage: \\! shell-command", INFO_ERROR); + return -1; + } + /* + 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; +} +#endif + + +static int +com_print(String *buffer,char *line __attribute__((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]; + my_bool save_rehash= opt_rehash; + int error; + + bzero(buff, 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, GET); + if (tmp && *tmp) + { + my_free(current_db); + current_db= my_strdup(PSI_NOT_INSTRUMENTED, tmp, MYF(MY_WME)); + tmp= get_arg(buff, GET_NEXT); + if (tmp) + { + my_free(current_host); + current_host=my_strdup(PSI_NOT_INSTRUMENTED, tmp,MYF(MY_WME)); + } + } + else + { + /* Quick re-connect */ + opt_rehash= 0; /* purecov: tested */ + } + buffer->length(0); // command used + } + else + opt_rehash= 0; + error=sql_connect(current_host,current_db,current_user,opt_password,0); + opt_rehash= save_rehash; + + if (connected) + { + sprintf(buff,"Connection id: %lu",mysql_thread_id(&mysql)); + 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 __attribute__((unused)), + char *line) +{ + char source_name[FN_REFLEN], *end, *param; + LINE_BUFFER *line_buff; + int error; + STATUS old_status; + FILE *sql_file; + my_bool save_ignore_errors; + + /* Skip space from file name */ + while (my_isspace(charset_info,*line)) + line++; + if (!(param = strchr(line, ' '))) // Skip command name + return put_info("Usage: \\. | source ", + INFO_ERROR, 0); + while (my_isspace(charset_info,*param)) + param++; + end=strmake_buf(source_name, param); + 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 */ + if (!(sql_file = my_fopen(source_name, O_RDONLY | O_BINARY,MYF(0)))) + { + char buff[FN_REFLEN+60]; + sprintf(buff,"Failed to open file '%s', error: %d", source_name,errno); + return put_info(buff, INFO_ERROR, 0); + } + + if (!(line_buff= batch_readline_init(MAX_BATCH_BUFFER_SIZE, sql_file))) + { + my_fclose(sql_file,MYF(0)); + return put_info("Can't initialize batch_readline", INFO_ERROR, 0); + } + + /* Save old status */ + old_status=status; + save_ignore_errors= ignore_errors; + bfill((char*) &status,sizeof(status),(char) 0); + + 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 + ignore_errors= !batch_abort_on_error; + in_com_source= 1; + error= read_and_execute(false); + ignore_errors= save_ignore_errors; + status=old_status; // Continue as before + in_com_source= aborted= 0; + my_fclose(sql_file,MYF(0)); + batch_readline_end(line_buff); + /* + If we got an error during source operation, don't abort the client + if ignore_errors is set + */ + if (error && ignore_errors) + error= -1; // Ignore error + return error; +} + + + /* ARGSUSED */ +static int +com_delimiter(String *buffer __attribute__((unused)), char *line) +{ + char buff[256], *tmp; + + strmake_buf(buff, line); + tmp= get_arg(buff, GET); + + if (!tmp || !*tmp) + { + put_info("DELIMITER must be followed by a 'delimiter' character or string", + INFO_ERROR); + return 0; + } + else + { + if (strstr(tmp, "\\")) + { + put_info("DELIMITER cannot contain a backslash character", INFO_ERROR); + return 0; + } + } + strmake_buf(delimiter, tmp); + delimiter_length= (int)strlen(delimiter); + delimiter_str= delimiter; + return 0; +} + + /* ARGSUSED */ +static int +com_use(String *buffer __attribute__((unused)), char *line) +{ + char *tmp, buff[FN_REFLEN + 1]; + int select_db; + + bzero(buff, sizeof(buff)); + strmake_buf(buff, line); + tmp= get_arg(buff, GET); + 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) + */ + get_current_db(); + + if (!current_db || cmp_database(charset_info, current_db,tmp)) + { + if (one_database) + { + skip_updates= 1; + 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= 0; + 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,tmp)) + { + if (mysql_errno(&mysql) != CR_SERVER_GONE_ERROR) + return put_error(&mysql); + + if (reconnect()) + return opt_reconnect ? -1 : 1; // Fatal error + if (mysql_select_db(&mysql,tmp)) + return put_error(&mysql); + } + 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, 1); +#endif + } + + put_info("Database changed",INFO_INFO); + return 0; +} + +static int +com_warnings(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + show_warnings = 1; + put_info("Show warnings enabled.",INFO_INFO); + return 0; +} + +static int +com_nowarnings(String *buffer __attribute__((unused)), + char *line __attribute__((unused))) +{ + show_warnings = 0; + put_info("Show warnings disabled.",INFO_INFO); + return 0; +} + +/* + Gets argument from a command on the command line. If mode is not GET_NEXT, + skips the command and returns the first argument. The line is modified by + adding zero to the end of the argument. If mode is GET_NEXT, 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. +*/ + +static char *get_arg(char *line, get_arg_mode mode) +{ + char *ptr, *start; + bool short_cmd= false; + char qtype= 0; + + ptr= line; + if (mode == GET_NEXT) + { + for (; *ptr; ptr++) ; + if (*(ptr + 1)) + ptr++; + } + else + { + /* skip leading white spaces */ + while (my_isspace(charset_info, *ptr)) + ptr++; + if ((short_cmd= *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; + ptr++; + } + for (start=ptr ; *ptr; ptr++) + { + /* if short_cmd use historical rules (only backslash) otherwise SQL rules */ + if (short_cmd + ? (*ptr == '\\' && ptr[1]) // escaped character + : (*ptr == '\\' && ptr[1] && qtype != '`') || // escaped character + (qtype && *ptr == qtype && ptr[1] == qtype)) // quote + { + // Remove (or skip) the backslash (or a second quote) + if (mode != CHECK) + strmov_overlapp(ptr, ptr+1); + else + ptr++; + } + else if (*ptr == (qtype ? qtype : ' ')) + { + qtype= 0; + if (mode != CHECK) + *ptr= 0; + break; + } + } + return ptr != start && !qtype ? start : NullS; +} + + +/** + An example of mysql_authentication_dialog_ask callback. + + The C function with the name "mysql_authentication_dialog_ask", if exists, + will be used by the "dialog" client authentication plugin when user + input is needed. This function should be of mysql_authentication_dialog_ask_t + type. If the function does not exists, a built-in implementation will be + used. + + @param mysql mysql + @param type type of the input + 1 - normal string input + 2 - password string + @param prompt prompt + @param buf a buffer to store the use input + @param buf_len the length of the buffer + + @retval a pointer to the user input string. + It may be equal to 'buf' or to 'mysql->password'. + In all other cases it is assumed to be an allocated + string, and the "dialog" plugin will free() it. +*/ + +extern "C" +#ifdef _MSC_VER +__declspec(dllexport) +#endif +char *mysql_authentication_dialog_ask(MYSQL *mysql, int type, + const char *prompt, + char *buf, int buf_len) +{ + char *s=buf; + + fputs("[mariadb] ", stdout); + fputs(prompt, stdout); + fputs(" ", stdout); + + if (type == 2) /* password */ + { + s= my_get_tty_password(""); + strnmov(buf, s, buf_len); + buf[buf_len-1]= 0; + my_free(s); + } + else + { + if (!fgets(buf, buf_len-1, stdin)) + buf[0]= 0; + else if (buf[0] && (s= strend(buf))[-1] == '\n') + s[-1]= 0; + } + + return buf; +} + +static int +sql_real_connect(char *host,char *database,char *user,char *password, + uint silent) +{ + const char *charset_name; + + if (connected) + { + connected= 0; + mysql_close(&mysql); + } + mysql_init(&mysql); + if (opt_init_command) + mysql_options(&mysql, MYSQL_INIT_COMMAND, opt_init_command); + if (opt_connect_timeout) + { + uint timeout=opt_connect_timeout; + mysql_options(&mysql,MYSQL_OPT_CONNECT_TIMEOUT, + (char*) &timeout); + } + if (opt_compress) + mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); + if (using_opt_local_infile) + mysql_options(&mysql,MYSQL_OPT_LOCAL_INFILE, (char*) &opt_local_infile); + 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 (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); + + my_bool can_handle_expired= opt_connect_expired_password || !status.batch; + mysql_options(&mysql, MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, &can_handle_expired); + + if (!do_connect(&mysql, host, user, password, database, + connect_flag | CLIENT_MULTI_STATEMENTS)) + { + if (!silent || + (mysql_errno(&mysql) != CR_CONN_HOST_ERROR && + mysql_errno(&mysql) != CR_CONNECTION_ERROR)) + { + (void) put_error(&mysql); + (void) fflush(stdout); + return ignore_errors ? -1 : 1; // Abort + } + return -1; // Retryable + } + + charset_name= IF_EMBEDDED(mysql.charset->coll_name.str, + mysql.charset->name); + charset_info= get_charset_by_name(charset_name, MYF(MY_UTF8_IS_UTF8MB3)); + if (!charset_info) + { + char buff[128]; + my_snprintf(buff, sizeof(buff)-1, + "Unknown default character set %s", charset_name); + put_info(buff, INFO_ERROR); + return 1; + } + adjust_console_codepage(charset_info->cs_name.str); + connected=1; +#ifndef EMBEDDED_LIBRARY + mysql_options(&mysql, MYSQL_OPT_RECONNECT, &debug_info_flag); + + /* + CLIENT_PROGRESS_OBSOLETE is set only if we requested it in + mysql_real_connect() and the server also supports it +*/ + if (mysql.client_flag & CLIENT_PROGRESS_OBSOLETE) + mysql_options(&mysql, MYSQL_PROGRESS_CALLBACK, (void*) report_progress); +#else + { + my_bool reconnect= 1; + mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect); + } +#endif +#ifdef HAVE_READLINE + build_completion_hash(opt_rehash, 1); +#endif + return 0; +} + + +static int +sql_connect(char *host,char *database,char *user,char *password,uint silent) +{ + bool message=0; + uint count=0; + int error; + for (;;) + { + if ((error=sql_real_connect(host,database,user,password,wait_flag)) >= 0) + { + if (count) + { + tee_fputs("\n", stderr); + (void) fflush(stderr); + } + return error; + } + if (!wait_flag) + return ignore_errors ? -1 : 1; + if (!message && !silent) + { + message=1; + 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 __attribute__((unused)), + char *line __attribute__((unused))) +{ + const char *status_str; + char buff[40]; + ulonglong id; + MYSQL_RES *UNINIT_VAR(result); + + if (mysql_real_query_for_lazy( + C_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)); + /* + 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); + } + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if ((status_str= mysql_get_ssl_cipher(&mysql))) + tee_fprintf(stdout, "SSL:\t\t\tCipher in use is %s\n", + status_str); + else +#endif /* HAVE_OPENSSL && !EMBEDDED_LIBRARY */ + tee_puts("SSL:\t\t\tNot in use", stdout); + + if (skip_updates) + { + my_vidattr(A_BOLD); + tee_fprintf(stdout, "\nAll updates ignored to this database\n"); + my_vidattr(A_NORMAL); + } +#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:\t\t\t%s\n", mysql_get_server_name(&mysql)); + tee_fprintf(stdout, "Server version:\t\t%s\n", server_version_string(&mysql)); + tee_fprintf(stdout, "Protocol version:\t%d\n", mysql_get_proto_info(&mysql)); + tee_fprintf(stdout, "Connection:\t\t%s\n", mysql_get_host_info(&mysql)); + if ((id= mysql_insert_id(&mysql))) + 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(C_STRING_WITH_LEN( + "select @@character_set_client, @@character_set_connection, " + "@@character_set_server, @@character_set_database limit 1"))) + { + if (mysql_errno(&mysql) == 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->cs_name.str); + tee_fprintf(stdout, "Server characterset:\t%s\n", + mysql_character_set_name(&mysql)); + } + +#ifndef EMBEDDED_LIBRARY + if (strstr(mysql_get_host_info(&mysql),"TCP/IP") || ! mysql.unix_socket) + tee_fprintf(stdout, "TCP port:\t\t%d\n", mysql.port); + else + tee_fprintf(stdout, "UNIX socket:\t\t%s\n", mysql.unix_socket); + if (mysql.net.compress) + tee_fprintf(stdout, "Protocol:\t\tCompressed\n"); +#endif + + const char *pos; + if ((status_str= mysql_stat(&mysql)) && !mysql_error(&mysql)[0] && + (pos= strchr(status_str,' '))) + { + ulong sec; + /* print label */ + tee_fprintf(stdout, "%.*s\t\t\t", (int) (pos-status_str), status_str); + if ((status_str= str2int(pos,10,0,LONG_MAX,(long*) &sec))) + { + nice_time((double) sec,buff,0); + 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) + { + my_vidattr(A_BOLD); + tee_fprintf(stdout, "\nNote that you are running in safe_update_mode:\n"); + my_vidattr(A_NORMAL); + 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 == NULL) + { + 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 */ + size_t len= strlen(mysql_get_server_info(con)) + strlen(cur[0]) + 2; + + if ((server_version= (char *) my_malloc(PSI_NOT_INSTRUMENTED, len, MYF(MY_WME)))) + { + char *bufp; + bufp = strmov(server_version, mysql_get_server_info(con)); + bufp = strmov(bufp, " "); + (void) strmov(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 == NULL) + 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(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) + { +#ifdef HAVE_SETUPTERM + int errret; + have_curses= setupterm((char *)0, 1, &errret) != ERR; +#endif + inited=1; + } + if (info_type == INFO_ERROR) + { + if (!opt_nobeep) + { +#ifdef _WIN32 + MessageBeep(MB_ICONWARNING); +#else + putchar('\a'); /* This should make a bell */ +#endif + } + my_vidattr(A_STANDOUT); + if (error) + { + if (sqlstate) + (void) tee_fprintf(file, "ERROR %d (%s)", error, sqlstate); + else + (void) tee_fprintf(file, "ERROR %d", error); + } + else + tee_fputs("ERROR", file); + 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); + } + tee_fputs(": ", file); + } + else + my_vidattr(A_BOLD); + (void) tee_puts(str, file); + my_vidattr(A_NORMAL); + } + if (unbuffered) + fflush(file); + return info_type == INFO_ERROR ? (ignore_errors ? -1 : 1): 0; +} + + +static int +put_error(MYSQL *con) +{ + return put_info(mysql_error(con), INFO_ERROR, mysql_errno(con), + mysql_sqlstate(con)); +} + + +static void remove_cntrl(String &buffer) +{ + char *start,*end; + end=(start=(char*) buffer.ptr())+buffer.length(); + while (start < end && !my_isgraph(charset_info,end[-1])) + end--; + buffer.length((uint) (end-start)); +} + + +void tee_fprintf(FILE *file, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + (void) vfprintf(file, fmt, args); + va_end(args); + + if (opt_outfile) + { + va_start(args, fmt); + (void) vfprintf(OUTFILE, fmt, args); + va_end(args); + } +} + + +void tee_fputs(const char *s, FILE *file) +{ + fputs(s, file); + if (opt_outfile) + fputs(s, OUTFILE); +} + + +void tee_puts(const char *s, FILE *file) +{ + fputs(s, file); + fputc('\n', file); + if (opt_outfile) + { + fputs(s, OUTFILE); + fputc('\n', OUTFILE); + } +} + +void tee_putc(int c, FILE *file) +{ + putc(c, file); + if (opt_outfile) + putc(c, OUTFILE); +} + + +/** + 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.000 seconds") -> 53 +*/ +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=int10_to_str((long) tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " days " : " day "); + } + if (sec >= 3600.0) + { + tmp=(ulong) floor(sec/3600.0); + sec-=3600.0*tmp; + buff=int10_to_str((long) tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " hours " : " hour "); + } + if (sec >= 60.0) + { + tmp=(ulong) floor(sec/60.0); + sec-=60.0*tmp; + buff=int10_to_str((long) tmp, buff, 10); + buff=strmov(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) +{ + double sec; + + buff[0]=' '; + buff[1]='('; + sec= (microsecond_interval_timer() - start_time) / (double) (1000 * 1000); + nice_time(sec, buff + 2, 1); + strmov(strend(buff),")"); +} + +static const char *construct_prompt() +{ + processed_prompt.free(); // Erase the old prompt + time_t lclock = time(NULL); // Get the date struct + struct tm *t = localtime(&lclock); + + /* parse through 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 'v': + { + const char *info= (connected ? + mysql_get_server_info(&mysql) : + "not_connected"); + processed_prompt.append(info, strlen(info)); + break; + } + case 'd': + { + const char *db= current_db ? current_db : "(none)"; + processed_prompt.append(db, strlen(db)); + break; + } + case 'N': + { + const char *name= (connected ? + mysql_get_server_name(&mysql) : + "unknown"); + processed_prompt.append(name, strlen(name)); + break; + } + case 'h': + case 'H': + { + const char *prompt; + prompt= connected ? mysql_get_host_info(&mysql) : "not_connected"; + if (strstr(prompt, "Localhost") || strstr(prompt, "localhost ")) + { + if (*c == 'h') + processed_prompt.append(STRING_WITH_LEN("localhost")); + else + { + static char hostname[FN_REFLEN]; + static size_t hostname_length; + if (hostname_length) + processed_prompt.append(hostname, hostname_length); + else if (gethostname(hostname, sizeof(hostname)) == 0) + { + hostname_length= strlen(hostname); + processed_prompt.append(hostname, hostname_length); + } + else + processed_prompt.append(STRING_WITH_LEN("gethostname(2) failed")); + } + } + else + { + const char *end=strcend(prompt,' '); + processed_prompt.append(prompt, (uint) (end-prompt)); + } + break; + } + case 'p': + { +#ifndef EMBEDDED_LIBRARY + if (!connected) + { + processed_prompt.append(STRING_WITH_LEN("not_connected")); + break; + } + + const char *host_info = mysql_get_host_info(&mysql); + if (strstr(host_info, "memory")) + { + processed_prompt.append( mysql.host, strlen(mysql.host)); + } + else if (strstr(host_info,"TCP/IP") || + !mysql.unix_socket) + add_int_to_prompt(mysql.port); + else + { + char *pos= strrchr(mysql.unix_socket,'/'); + const char *tmp= pos ? pos+1 : mysql.unix_socket; + processed_prompt.append(tmp, strlen(tmp)); + } +#endif + } + break; + case 'U': + { + const char *name; + if (!full_username) + init_username(); + name= (full_username ? full_username : + (current_user ? current_user : "(unknown)")); + processed_prompt.append(name, strlen(name)); + break; + } + case 'u': + { + const char *name; + if (!full_username) + init_username(); + name= (part_username ? part_username : + (current_user ? current_user : "(unknown)")); + processed_prompt.append(name, strlen(name)); + 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; + const char *tmp; + dateTime = ctime(&lclock); + tmp= strtok(dateTime,"\n"); + processed_prompt.append(tmp, strlen(tmp)); + break; + } + case 's': + if (t->tm_sec < 10) + processed_prompt.append('0'); + add_int_to_prompt(t->tm_sec); + break; + case 'w': + { + const char *name= day_names[t->tm_wday]; + processed_prompt.append(name, strlen(name)); + break; + } + case 'P': + processed_prompt.append(t->tm_hour < 12 ? "am" : "pm", 2); + break; + case 'o': + add_int_to_prompt(t->tm_mon+1); + break; + case 'O': + { + const char *name= month_names[t->tm_mon]; + processed_prompt.append(name, strlen(name)); + 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, strlen(delimiter_str)); + break; + default: + processed_prompt.append(*c); + } + } + } + processed_prompt.append('\0'); + return processed_prompt.ptr(); +} + + +static void add_int_to_prompt(int toadd) +{ + char buffer[16]; + size_t length= (size_t) (int10_to_str(toadd,buffer,10) - buffer); + processed_prompt.append(buffer, length); +} + +static void init_username() +{ + my_free(full_username); + my_free(part_username); + + MYSQL_RES *UNINIT_VAR(result); + if (!mysql_query(&mysql,"select USER()") && + (result=mysql_use_result(&mysql))) + { + 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 + mysql_free_result(result); + } +} + +static int com_prompt(String *buffer __attribute__((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; +} + +#ifndef EMBEDDED_LIBRARY +static void report_progress(const MYSQL *mysql, uint stage, uint max_stage, + double progress, const char *proc_info, + uint proc_info_length) +{ + uint length= printf("Stage: %d of %d '%.*s' %6.3g%% of stage done", + stage, max_stage, proc_info_length, proc_info, + progress); + if (length < last_progress_report_length) + printf("%*s", last_progress_report_length - length, ""); + putc('\r', stdout); + fflush(stdout); + last_progress_report_length= length; +} + +static void report_progress_end() +{ + if (last_progress_report_length) + { + printf("%*s\r", last_progress_report_length, ""); + last_progress_report_length= 0; + } +} +#else +static void report_progress_end() +{ +} +#endif diff --git a/client/mysql_plugin.c b/client/mysql_plugin.c new file mode 100644 index 00000000..a96af015 --- /dev/null +++ b/client/mysql_plugin.c @@ -0,0 +1,1244 @@ +/* + Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +#include +#include +#include +#include +#include +#include + +#define SHOW_VERSION "1.0.0" +#define PRINT_VERSION do { printf("%s Ver %s Distrib %s\n", \ + my_progname, SHOW_VERSION, MYSQL_SERVER_VERSION); \ + } while(0) + +/* Global variables. */ +static uint my_end_arg= 0; +static uint opt_verbose=0; +static uint opt_no_defaults= 0; +static uint opt_print_defaults= 0; +static char *opt_datadir=0, *opt_basedir=0, + *opt_plugin_dir=0, *opt_plugin_ini=0, + *opt_mysqld=0, *opt_my_print_defaults=0, *opt_lc_messages_dir; +static char bootstrap[FN_REFLEN]; + + +/* plugin struct */ +struct st_plugin +{ + const char *name; /* plugin name */ + const char *so_name; /* plugin so (library) name */ + const char *components[16]; /* components to load */ +} plugin_data; + + +/* Options */ +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"basedir", 'b', "The basedir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"datadir", 'd', "The datadir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin-dir", 'p', "The plugin dir for the server.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin-ini", 'i', "Read plugin information from configuration file " + "specified instead of from /.ini.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"no-defaults", 'n', "Do not read values from configuration file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"print-defaults", 'P', "Show default values from configuration file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"mysqld", 'm', "Path to mysqld executable. Example: /sbin/temp1/mysql/bin", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"my-print-defaults", 'f', "Path to my_print_defaults executable. " + "Example: /source/temp11/extra", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"lc-messages-dir", 'l', "The error messages dir for the server. ", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', + "More verbose output; you can use this multiple times to get even more " + "verbose output.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +/* Methods */ +static int process_options(int argc, char *argv[], char *operation); +static int check_access(); +static int find_tool(const char *tool_name, char *tool_path); +static int find_plugin(char *tp_path); +static int build_bootstrap_file(char *operation, char *bootstrap); +static int dump_bootstrap_file(char *bootstrap_file); +static int bootstrap_server(char *server_path, char *bootstrap_file); + + +int main(int argc,char *argv[]) +{ + int error= 0; + char tp_path[FN_REFLEN]; + char server_path[FN_REFLEN]; + char operation[16]; + + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + plugin_data.name= 0; /* initialize name */ + + /* + The following operations comprise the method for enabling or disabling + a plugin. We begin by processing the command options then check the + directories specified for --datadir, --basedir, --plugin-dir, and + --plugin-ini (if specified). If the directories are Ok, we then look + for the mysqld executable and the plugin soname. Finally, we build a + bootstrap command file for use in bootstraping the server. + + If any step fails, the method issues an error message and the tool exits. + + 1) Parse, execute, and verify command options. + 2) Check access to directories. + 3) Look for mysqld executable. + 4) Look for the plugin. + 5) Build a bootstrap file with commands to enable or disable plugin. + + */ + if ((error= process_options(argc, argv, operation)) || + (error= check_access()) || + (error= find_tool("mysqld" FN_EXEEXT, server_path)) || + (error= find_plugin(tp_path)) || + (error= build_bootstrap_file(operation, bootstrap))) + goto exit; + + /* Dump the bootstrap file if --verbose specified. */ + if (opt_verbose && ((error= dump_bootstrap_file(bootstrap)))) + goto exit; + + /* Start the server in bootstrap mode and execute bootstrap commands */ + error= bootstrap_server(server_path, bootstrap); + +exit: + /* Remove file */ + my_delete(bootstrap, MYF(0)); + if (opt_verbose && error == 0) + { + printf("# Operation succeeded.\n"); + } + + my_end(my_end_arg); + exit(error ? 1 : 0); + return 0; /* No compiler warnings */ +} + + +/** + Get a temporary file name. + + @param[out] filename The file name of the temporary file + @param[in] ext An extension for the file (optional) + + @retval int error = 1, success = 0 +*/ + +static int make_tempfile(char *filename, const char *ext) +{ + int fd= 0; + + if ((fd= create_temp_file(filename, NullS, ext, 0, MYF(MY_WME))) < 0) + { + fprintf(stderr, "ERROR: Cannot generate temporary file. Error code: %d.\n", + fd); + return 1; + } + my_close(fd, MYF(0)); + return 0; +} + + +/** + Get the value of an option from a string read from my_print_defaults output. + + @param[in] line The line (string) read from the file + @param[in] item The option to search for (e.g. --datadir) + + @returns NULL if not found, string containing value if found +*/ + +static char *get_value(char *line, const char *item) +{ + char *destination= 0; + int item_len= (int)strlen(item); + int line_len = (int)strlen(line); + + if ((strncasecmp(line, item, item_len) == 0)) + { + int start= 0; + char *s= 0; + + s = line + item_len + 1; + destination= my_strndup(PSI_NOT_INSTRUMENTED, s, line_len - start, MYF(MY_FAE)); + destination[line_len - item_len - 2]= 0; + } + return destination; +} + + +/** + Run a command in a shell. + + This function will attempt to execute the command specified by using the + popen() method to open a shell and execute the command passed and store the + output in a result file. If the --verbose option was specified, it will open + the result file and print the contents to stdout. + + @param[in] cmd The command to execute. + @param[in] mode The mode for popen() (e.g. "r", "w", "rw") + + @return int error code or 0 for success. +*/ + +static int run_command(char* cmd, const char *mode) +{ + char buf[512]= {0}; + FILE *res_file; + int error; + + if (!(res_file= popen(cmd, mode))) + return -1; + + if (opt_verbose) + { + while (fgets(buf, sizeof(buf), res_file)) + { + fprintf(stdout, "%s", buf); + } + } + error= pclose(res_file); + return error; +} + + +#ifdef _WIN32 +/** + Check to see if there are spaces in a path. + + @param[in] path The Windows path to examine. + + @retval int spaces found = 1, no spaces = 0 +*/ +static int has_spaces(const char *path) +{ + if (strchr(path, ' ') != NULL) + return 1; + return 0; +} + + +/** + Convert a Unix path to a Windows path. + + @param[in] path The Windows path to examine. + + @returns string containing path with / changed to \\ +*/ +static char *convert_path(const char *argument) +{ + /* Convert / to \\ to make Windows paths */ + char *winfilename= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + char *pos, *end; + size_t length= strlen(argument); + + for (pos= winfilename, end= pos+length ; pos < end ; pos++) + { + if (*pos == '/') + { + *pos= '\\'; + } + } + return winfilename; +} + + +/** + Add quotes if the path has spaces in it. + + @param[in] path The Windows path to examine. + + @returns string containing escaped quotes if spaces found in path +*/ +static char *add_quotes(const char *path) +{ + char windows_cmd_friendly[FN_REFLEN]; + + if (has_spaces(path)) + snprintf(windows_cmd_friendly, sizeof(windows_cmd_friendly), + "\"%s\"", path); + else + snprintf(windows_cmd_friendly, sizeof(windows_cmd_friendly), + "%s", path); + return my_strdup(PSI_NOT_INSTRUMENTED, windows_cmd_friendly, MYF(MY_FAE)); +} +#endif + + +/** + Get the default values from the my.cnf file. + + This method gets the default values for the following parameters: + + --datadir + --basedir + --plugin-dir + --plugin-ini + --lc-messages-dir + + These values are used if the user has not specified a value. + + @retval int error = 1, success = 0 +*/ + +static int get_default_values() +{ + char tool_path[FN_REFLEN]; + char defaults_cmd[FN_REFLEN]; + char defaults_file[FN_REFLEN]; + char line[FN_REFLEN]; + int error= 0; + int ret= 0; + FILE *file= 0; + + memset(tool_path, 0, FN_REFLEN); + if ((error= find_tool("my_print_defaults" FN_EXEEXT, tool_path))) + goto exit; + else + { + if ((error= make_tempfile(defaults_file, "txt"))) + goto exit; + +#ifdef _WIN32 + { + char *format_str= 0; + + if (has_spaces(tool_path) || has_spaces(defaults_file)) + format_str = "\"%s --mysqld > %s\""; + else + format_str = "%s --mysqld > %s"; + + snprintf(defaults_cmd, sizeof(defaults_cmd), format_str, + add_quotes(tool_path), add_quotes(defaults_file)); + if (opt_verbose) + { + printf("# my_print_defaults found: %s\n", tool_path); + } + } +#else + snprintf(defaults_cmd, sizeof(defaults_cmd), + "%s --mysqld > %s", tool_path, defaults_file); +#endif + + /* Execute the command */ + if (opt_verbose) + { + printf("# Command: %s\n", defaults_cmd); + } + error= run_command(defaults_cmd, "r"); + if (error) + { + fprintf(stderr, "ERROR: my_print_defaults failed. Error code: %d.\n", + ret); + goto exit; + } + /* Now open the file and read the defaults we want. */ + file= fopen(defaults_file, "r"); + if (file == NULL) + { + fprintf(stderr, "ERROR: failed to open file %s: %s.\n", defaults_file, + strerror(errno)); + goto exit; + } + while (fgets(line, FN_REFLEN, file) != NULL) + { + char *value= 0; + + if ((opt_datadir == 0) && ((value= get_value(line, "--datadir")))) + { + opt_datadir= my_strdup(PSI_NOT_INSTRUMENTED, value, MYF(MY_FAE)); + } + if ((opt_basedir == 0) && ((value= get_value(line, "--basedir")))) + { + opt_basedir= my_strdup(PSI_NOT_INSTRUMENTED, value, MYF(MY_FAE)); + } + if ((opt_plugin_dir == 0) && ((value= get_value(line, "--plugin_dir")) || + (value= get_value(line, "--plugin-dir")))) + { + opt_plugin_dir= my_strdup(PSI_NOT_INSTRUMENTED, value, MYF(MY_FAE)); + } + if ((opt_lc_messages_dir == 0) && + ((value= get_value(line, "--lc_messages_dir")) || + (value= get_value(line, "--lc_messages-dir")) || + (value= get_value(line, "--lc-messages_dir")) || + (value= get_value(line, "--lc-messages-dir")))) + { + opt_lc_messages_dir= my_strdup(PSI_NOT_INSTRUMENTED, value, MYF(MY_FAE)); + } + + } + } +exit: + if (file) + { + fclose(file); + /* Remove file */ + my_delete(defaults_file, MYF(0)); + } + return error; +} + + +/** + Print usage. +*/ + +static void usage(void) +{ + PRINT_VERSION; + puts("Copyright (c) 2011, 2015, Oracle and/or its affiliates. " + "All rights reserved.\n"); + puts("Enable or disable plugins."); + printf("\nUsage: %s [options] ENABLE|DISABLE\n\nOptions:\n", + my_progname); + my_print_help(my_long_options); + puts("\n"); +} + + +/** + Print the default values as read from the my.cnf file. + + This method displays the default values for the following parameters: + + --datadir + --basedir + --plugin-dir + --plugin-ini + --lc-messages-dir + +*/ + +static void print_default_values(void) +{ + printf("%s would have been started with the following arguments:\n", + my_progname); + get_default_values(); + if (opt_datadir) + { + printf("--datadir=%s ", opt_datadir); + } + if (opt_basedir) + { + printf("--basedir=%s ", opt_basedir); + } + if (opt_plugin_dir) + { + printf("--plugin_dir=%s ", opt_plugin_dir); + } + if (opt_plugin_ini) + { + printf("--plugin_ini=%s ", opt_plugin_ini); + } + if (opt_mysqld) + { + printf("--mysqld=%s ", opt_mysqld); + } + if (opt_my_print_defaults) + { + printf("--my_print_defaults=%s ", opt_my_print_defaults); + } + if (opt_lc_messages_dir) + { + printf("--lc_messages_dir=%s ", opt_lc_messages_dir); + } + printf("\n"); +} + + +/** + Process the arguments and identify an option and store its value. + + @param[in] optid The single character shortcut for the argument. + @param[in] my_option Structure of legal options. + @param[in] argument The argument value to process. +*/ + +static my_bool +get_one_option(const struct my_option *opt, + const char *argument, + const char *filename __attribute__((unused))) +{ + switch(opt->id) { + case 'n': + opt_no_defaults++; + break; + case 'P': + opt_print_defaults++; + print_default_values(); + break; + case 'v': + opt_verbose++; + break; + case 'V': + PRINT_VERSION; + exit(0); + break; + case '?': + case 'I': /* Info */ + usage(); + exit(0); + case 'd': + opt_datadir= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'b': + opt_basedir= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'p': + opt_plugin_dir= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'i': + opt_plugin_ini= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'm': + opt_mysqld= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'f': + opt_my_print_defaults= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + case 'l': + opt_lc_messages_dir= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + break; + + } + return 0; +} + + +/** + Check to see if a file exists. + + @param[in] filename File to locate. + + @retval int file not found = 1, file found = 0 +*/ + +static int file_exists(char * filename) +{ + MY_STAT stat_arg; + + if (!my_stat(filename, &stat_arg, MYF(0))) + { + return 0; + } + return 1; +} + + +/** + Search a specific path and sub directory for a file name. + + @param[in] base_path Original path to use. + @param[in] tool_name Name of the tool to locate. + @param[in] subdir The sub directory to search. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int search_dir(const char *base_path, const char *tool_name, + const char *subdir, char *tool_path) +{ + char new_path[FN_REFLEN]; + char source_path[FN_REFLEN]; + + safe_strcpy(source_path, sizeof(source_path), base_path); + safe_strcat(source_path, sizeof(source_path), subdir); + fn_format(new_path, tool_name, source_path, "", MY_UNPACK_FILENAME); + if (file_exists(new_path)) + { + strcpy(tool_path, new_path); + return 1; + } + return 0; +} + + +/** + Search known common paths and sub directories for a file name. + + @param[in] base_path Original path to use. + @param[in] tool_name Name of the tool to locate. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int search_paths(const char *base_path, const char *tool_name, + char *tool_path) +{ + int i= 0; + + static const char *paths[]= { + "", "/share/", "/scripts/", "/bin/", "/sbin/", "/libexec/", + "/mysql/", "/sql/", + }; + for (i = 0 ; i < (int)array_elements(paths); i++) + { + if (search_dir(base_path, tool_name, paths[i], tool_path)) + { + return 1; + } + } + return 0; +} + + +/** + Read the plugin ini file. + + This function attempts to read the plugin config file from the plugin_dir + path saving the data in the the st_plugin structure. If the file is not + found or the file cannot be read, an error is generated. + + @retval int error = 1, success = 0 +*/ + +static int load_plugin_data(char *plugin_name, char *config_file) +{ + FILE *file_ptr; + char path[FN_REFLEN]; + char line[1024]; + const char *reason= 0; + char *res; + int i= -1; + + if (opt_plugin_ini == 0) + { + fn_format(path, config_file, opt_plugin_dir, "", MYF(0)); + opt_plugin_ini= my_strdup(PSI_NOT_INSTRUMENTED, path, MYF(MY_FAE)); + } + if (!file_exists(opt_plugin_ini)) + { + reason= "File does not exist."; + goto error; + } + + file_ptr= fopen(opt_plugin_ini, "r"); + if (file_ptr == NULL) + { + reason= "Cannot open file."; + goto error; + } + + /* save name */ + plugin_data.name= my_strdup(PSI_NOT_INSTRUMENTED, plugin_name, MYF(MY_WME)); + + /* Read plugin components */ + while (i < 16) + { + size_t line_len; + + res= fgets(line, sizeof(line), file_ptr); + line_len= strlen(line); + + /* strip /n */ + if (line[line_len - 1] == '\n') + line[line_len - 1]= '\0'; + + if (res == NULL) + { + if (i < 1) + { + reason= "Bad format in plugin configuration file."; + fclose(file_ptr); + goto error; + } + break; + } + if ((line[0] == '#') || (line[0] == '\n')) /* skip comment and blank lines */ + { + continue; + } + if (i == -1) /* if first pass, read this line as so_name */ + { + /* Add proper file extension for soname */ + if (safe_strcpy(line + line_len - 1, sizeof(line), FN_SOEXT)) + { + reason= "Plugin name too long."; + fclose(file_ptr); + goto error; + } + /* save so_name */ + plugin_data.so_name= my_strdup(PSI_NOT_INSTRUMENTED, line, MYF(MY_WME|MY_ZEROFILL)); + i++; + } + else + { + if (line_len > 0) + { + plugin_data.components[i]= my_strdup(PSI_NOT_INSTRUMENTED, line, MYF(MY_WME)); + i++; + } + else + { + plugin_data.components[i]= NULL; + } + } + } + + fclose(file_ptr); + return 0; + +error: + fprintf(stderr, "ERROR: Cannot read plugin config file %s. %s\n", + plugin_name, reason); + return 1; +} + + +/** + Check the options for validity. + + This function checks the arguments for validity issuing the appropriate + error message if arguments are missing or invalid. On success, @operation + is set to either "ENABLE" or "DISABLE". + + @param[in] argc The number of arguments. + @param[in] argv The arguments. + @param[out] operation The operation chosen (enable|disable) + + @retval int error = 1, success = 0 +*/ + +static int check_options(int argc, char **argv, char *operation) +{ + int i= 0; /* loop counter */ + int num_found= 0; /* number of options found (shortcut loop) */ + char config_file[FN_REFLEN]; /* configuration file name */ + char plugin_name[FN_REFLEN]; /* plugin name */ + + /* Form prefix strings for the options. */ + const char *basedir_prefix = "--basedir="; + size_t basedir_len= strlen(basedir_prefix); + const char *datadir_prefix = "--datadir="; + size_t datadir_len= strlen(datadir_prefix); + const char *plugin_dir_prefix = "--plugin_dir="; + size_t plugin_dir_len= strlen(plugin_dir_prefix); + + strcpy(plugin_name, ""); + for (i = 0; i < argc && num_found < 5; i++) + { + + if (!argv[i]) + { + continue; + } + if ((strcasecmp(argv[i], "ENABLE") == 0) || + (strcasecmp(argv[i], "DISABLE") == 0)) + { + strcpy(operation, argv[i]); + num_found++; + } + else if ((strncasecmp(argv[i], basedir_prefix, basedir_len) == 0) && + !opt_basedir) + { + opt_basedir= my_strndup(PSI_NOT_INSTRUMENTED, argv[i]+basedir_len, + strlen(argv[i])-basedir_len, MYF(MY_FAE)); + num_found++; + } + else if ((strncasecmp(argv[i], datadir_prefix, datadir_len) == 0) && + !opt_datadir) + { + opt_datadir= my_strndup(PSI_NOT_INSTRUMENTED, argv[i]+datadir_len, + strlen(argv[i])-datadir_len, MYF(MY_FAE)); + num_found++; + } + else if ((strncasecmp(argv[i], plugin_dir_prefix, plugin_dir_len) == 0) && + !opt_plugin_dir) + { + opt_plugin_dir= my_strndup(PSI_NOT_INSTRUMENTED, argv[i]+plugin_dir_len, + strlen(argv[i])-plugin_dir_len, MYF(MY_FAE)); + num_found++; + } + /* read the plugin config file and check for match against argument */ + else + { + if (safe_strcpy(plugin_name, sizeof(plugin_name), argv[i]) || + safe_strcpy(config_file, sizeof(config_file), argv[i]) || + safe_strcat(config_file, sizeof(config_file), ".ini")) + { + fprintf(stderr, "ERROR: argument is too long.\n"); + return 1; + } + } + } + + if (!opt_basedir) + { + fprintf(stderr, "ERROR: Missing --basedir option.\n"); + return 1; + } + + if (!opt_datadir) + { + fprintf(stderr, "ERROR: Missing --datadir option.\n"); + return 1; + } + + if (!opt_plugin_dir) + { + fprintf(stderr, "ERROR: Missing --plugin_dir option.\n"); + return 1; + } + /* If a plugin was specified, read the config file. */ + else if (strlen(plugin_name) > 0) + { + if (load_plugin_data(plugin_name, config_file)) + { + return 1; + } + if (strcasecmp(plugin_data.name, plugin_name) != 0) + { + fprintf(stderr, "ERROR: plugin name requested does not match config " + "file data.\n"); + return 1; + } + } + else + { + fprintf(stderr, "ERROR: No plugin specified.\n"); + return 1; + } + + if ((strlen(operation) == 0)) + { + fprintf(stderr, "ERROR: missing operation. Please specify either " + "' ENABLE' or ' DISABLE'.\n"); + return 1; + } + + return 0; +} + + +/** + Parse, execute, and verify command options. + + This method handles all of the option processing including the optional + features for displaying data (--print-defaults, --help ,etc.) that do not + result in an attempt to ENABLE or DISABLE of a plugin. + + @param[in] arc Count of arguments + @param[in] argv Array of arguments + @param[out] operation Operation (ENABLE or DISABLE) + + @retval int error = 1, success = 0, exit program = -1 +*/ + +static int process_options(int argc, char *argv[], char *operation) +{ + int error= 0; + + /* Parse and execute command-line options */ + if ((error= handle_options(&argc, &argv, my_long_options, get_one_option))) + return error; + + /* If the print defaults option used, exit. */ + if (opt_print_defaults) + return -1; + + /* Add a trailing directory separator if not present */ + if (opt_basedir) + { + size_t basedir_len= strlength(opt_basedir); + if (opt_basedir[basedir_len - 1] != FN_LIBCHAR || + opt_basedir[basedir_len - 1] != FN_LIBCHAR2) + { + char buff[FN_REFLEN]; + if (basedir_len + 2 > FN_REFLEN) + return -1; + + memcpy(buff, opt_basedir, basedir_len); + buff[basedir_len]= '/'; + buff[basedir_len + 1]= '\0'; + + my_free(opt_basedir); + opt_basedir= my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE)); + } + } + + /* + If the user did not specify the option to skip loading defaults from a + config file and the required options are not present or there was an error + generated when the defaults were read from the file, exit. + */ + if (!opt_no_defaults && ((error= get_default_values()))) + return -1; + + /* + Check to ensure required options are present and validate the operation. + Note: this method also validates the plugin specified by attempting to + read a configuration file named .ini from the --plugin-dir + or --plugin-ini location if the --plugin-ini option presented. + */ + operation[0]= '\0'; + if ((error= check_options(argc, argv, operation))) + return error; + + if (opt_verbose) + { + printf("# basedir = %s\n", opt_basedir); + printf("# plugin_dir = %s\n", opt_plugin_dir); + printf("# datadir = %s\n", opt_datadir); + printf("# plugin_ini = %s\n", opt_plugin_ini); + if (opt_lc_messages_dir != 0) + printf("# lc_messages_dir = %s\n", opt_lc_messages_dir); + } + + return 0; +} + + +/** + Check access + + This method checks to ensure all of the directories (opt_basedir, + opt_plugin_dir, opt_datadir, and opt_plugin_ini) are accessible by + the user. + + @retval int error = 1, success = 0 +*/ + +static int check_access() +{ + int error= 0; + + if ((error= my_access(opt_basedir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access basedir at '%s'.\n", + opt_basedir); + goto exit; + } + if ((error= my_access(opt_plugin_dir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access plugin_dir at '%s'.\n", + opt_plugin_dir); + goto exit; + } + if ((error= my_access(opt_datadir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access datadir at '%s'.\n", + opt_datadir); + goto exit; + } + if (opt_plugin_ini && (error= my_access(opt_plugin_ini, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access plugin config file at '%s'.\n", + opt_plugin_ini); + goto exit; + } + if (opt_mysqld && (error= my_access(opt_mysqld, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access mariadbd path '%s'.\n", + opt_mysqld); + goto exit; + } + if (opt_my_print_defaults && (error= my_access(opt_my_print_defaults, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access my-print-defaults path '%s'.\n", + opt_my_print_defaults); + goto exit; + } + if (opt_lc_messages_dir && (error= my_access(opt_lc_messages_dir, F_OK))) + { + fprintf(stderr, "ERROR: Cannot access lc-messages-dir path '%s'.\n", + opt_lc_messages_dir); + goto exit; + } + +exit: + return error; +} + + +/** + Locate the tool and form tool path. + + @param[in] tool_name Name of the tool to locate. + @param[out] tool_path If tool found, return complete path. + + @retval int error = 1, success = 0 +*/ + +static int find_tool(const char *tool_name, char *tool_path) +{ + int i= 0; + + const char *paths[]= { + opt_mysqld, opt_basedir, opt_my_print_defaults, "/usr", + "/usr/local/mysql", "/usr/sbin", "/usr/share", "/extra", "/extra/debug", + "/extra/release", "/bin", "/usr/bin", "/mysql/bin" + }; + for (i= 0; i < (int)array_elements(paths); i++) + { + if (paths[i] && (search_paths(paths[i], tool_name, tool_path))) + goto found; + } + fprintf(stderr, "WARNING: Cannot find %s.\n", tool_name); + return 1; +found: + if (opt_verbose) + printf("# Found tool '%s' as '%s'.\n", tool_name, tool_path); + return 0; +} + + +/** + Find the plugin library. + + This function attempts to use the @c plugin_dir option passed on the + command line to locate the plugin. + + @param[out] tp_path The actual path to plugin with FN_SOEXT applied. + + @retval int error = 1, success = 0 +*/ + +static int find_plugin(char *tp_path) +{ + /* Check for existence of plugin */ + fn_format(tp_path, plugin_data.so_name, opt_plugin_dir, "", MYF(0)); + if (!file_exists(tp_path)) + { + fprintf(stderr, "ERROR: The plugin library is missing or in a different" + " location.\n"); + return 1; + } + else if (opt_verbose) + { + printf("# Found plugin '%s' as '%s'\n", plugin_data.name, tp_path); + } + return 0; +} + + +/** + Build the bootstrap file. + + Create a new file and populate it with SQL commands to ENABLE or DISABLE + the plugin via REPLACE and DELETE operations on the mysql.plugin table. + + param[in] operation The type of operation (ENABLE or DISABLE) + param[out] bootstrap A FILE* pointer + + @retval int error = 1, success = 0 +*/ + +static int build_bootstrap_file(char *operation, char *bootstrap) +{ + int error= 0; + FILE *file= 0; + + /* + Perform plugin operation : ENABLE or DISABLE + + The following creates a temporary bootstrap file and populates it with + the appropriate SQL commands for the operation. For ENABLE, REPLACE + statements are created. For DISABLE, DELETE statements are created. The + values for these statements are derived from the plugin_data read from the + .ini configuration file. Once the file is built, a call to + mysqld is made in read only, bootstrap modes to read the SQL statements + and execute them. + + Note: Replace was used so that if a user loads a newer version of a + library with a different library name, the new library name is + used for symbols that match. + */ + if ((error= make_tempfile(bootstrap, "sql"))) + { + /* Fail if we cannot create a temporary file for the bootstrap commands. */ + fprintf(stderr, "ERROR: Cannot create bootstrap file.\n"); + goto exit; + } + if ((file= fopen(bootstrap, "w+")) == NULL) + { + fprintf(stderr, "ERROR: Cannot open bootstrap file for writing.\n"); + error= 1; + goto exit; + } + if (strcasecmp(operation, "enable") == 0) + { + int i= 0; + fprintf(file, "REPLACE INTO mysql.plugin VALUES "); + for (i= 0; i < (int)array_elements(plugin_data.components); i++) + { + /* stop when we read the end of the symbol list - marked with NULL */ + if (plugin_data.components[i] == NULL) + { + break; + } + if (i > 0) + { + fprintf(file, ", "); + } + fprintf(file, "('%s','%s')", + plugin_data.components[i], plugin_data.so_name); + } + fprintf(file, ";\n"); + if (opt_verbose) + { + printf("# Enabling %s...\n", plugin_data.name); + } + } + else + { + fprintf(file, + "DELETE FROM mysql.plugin WHERE dl = '%s';", plugin_data.so_name); + if (opt_verbose) + { + printf("# Disabling %s...\n", plugin_data.name); + } + } + +exit: + fclose(file); + return error; +} + + +/** + Dump bootstrap file. + + Read the contents of the bootstrap file and print it out. + + @param[in] bootstrap_file Name of bootstrap file to read + + @retval int error = 1, success = 0 +*/ + +static int dump_bootstrap_file(char *bootstrap_file) +{ + char *ret= 0; + int error= 0; + char query_str[512]; + FILE *file= 0; + + if ((file= fopen(bootstrap_file, "r")) == NULL) + { + fprintf(stderr, "ERROR: Cannot open bootstrap file for reading.\n"); + error= 1; + goto exit; + } + ret= fgets(query_str, 512, file); + if (ret == 0) + { + fprintf(stderr, "ERROR: Cannot read bootstrap file.\n"); + error= 1; + goto exit; + } + printf("# Query: %s\n", query_str); + +exit: + if (file) + { + fclose(file); + } + return error; +} + + +/** + Bootstrap the server + + Create a command line sequence to launch mysqld in bootstrap mode. This + will allow mysqld to launch a minimal server instance to read and + execute SQL commands from a file piped in (the bootstrap file). We use + the --no-defaults option to skip reading values from the config file. + + The bootstrap mode skips loading of plugins and many other subsystems. + This allows the mysql_plugin tool to insert the correct rows into the + mysql.plugin table (for ENABLE) or delete the rows (for DISABLE). Once + the server is launched in normal mode, the plugin will be loaded + (for ENABLE) or not loaded (for DISABLE). In this way, we avoid the + (sometimes) complicated LOAD PLUGIN commands. + + @param[in] server_path Path to server executable + @param[in] bootstrap_file Name of bootstrap file to read + + @retval int error = 1, success = 0 +*/ + +static int bootstrap_server(char *server_path, char *bootstrap_file) +{ + char bootstrap_cmd[FN_REFLEN]= {0}; + char lc_messages_dir_str[FN_REFLEN]= {0}; + int error= 0; + +#ifdef _WIN32 + char *format_str= 0; + const char *verbose_str= NULL; +#endif + + if (opt_lc_messages_dir != NULL) + snprintf(lc_messages_dir_str, sizeof(lc_messages_dir_str), "--lc-messages-dir=%s", + opt_lc_messages_dir); + +#ifdef _WIN32 + if (opt_verbose) + verbose_str= "--console"; + else + verbose_str= ""; + + if (has_spaces(opt_datadir) || has_spaces(opt_basedir) || + has_spaces(bootstrap_file) || has_spaces(lc_messages_dir_str)) + format_str= "\"%s %s --bootstrap --datadir=%s --basedir=%s %s <%s\""; + else + format_str= "%s %s --bootstrap --datadir=%s --basedir=%s %s <%s"; + + snprintf(bootstrap_cmd, sizeof(bootstrap_cmd), format_str, + add_quotes(convert_path(server_path)), verbose_str, + add_quotes(opt_datadir), add_quotes(opt_basedir), + add_quotes(lc_messages_dir_str), add_quotes(bootstrap_file)); +#else + snprintf(bootstrap_cmd, sizeof(bootstrap_cmd), + "%s --no-defaults --bootstrap --datadir=%s --basedir=%s %s" + " <%s", server_path, opt_datadir, opt_basedir, lc_messages_dir_str, bootstrap_file); +#endif + + /* Execute the command */ + if (opt_verbose) + { + printf("# Command: %s\n", bootstrap_cmd); + } + error= run_command(bootstrap_cmd, "r"); + if (error) + fprintf(stderr, + "ERROR: Unexpected result from bootstrap. Error code: %d.\n", + error); + + return error; +} diff --git a/client/mysql_upgrade.c b/client/mysql_upgrade.c new file mode 100644 index 00000000..a6d497b2 --- /dev/null +++ b/client/mysql_upgrade.c @@ -0,0 +1,1524 @@ +/* + Copyright (c) 2006, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2017, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +#include "client_priv.h" +#include +#include <../scripts/mysql_fix_privilege_tables_sql.c> + +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +#define VER "2.1" + +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#ifndef WEXITSTATUS +# ifdef _WIN32 +# define WEXITSTATUS(stat_val) (stat_val) +# else +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +# endif +#endif + +static int phase = 0; +static int info_file= -1; +static const int phases_total = 8; +static char mysql_path[FN_REFLEN]; +static char mysqlcheck_path[FN_REFLEN]; + +static my_bool debug_info_flag, debug_check_flag, + opt_systables_only, opt_version_check; +static my_bool opt_not_used, opt_silent, opt_check_upgrade; +static uint opt_force, opt_verbose; +static uint my_end_arg= 0; +static char *opt_user= (char*)"root"; + +static my_bool upgrade_from_mysql; + +static DYNAMIC_STRING ds_args; +static DYNAMIC_STRING conn_args; +static DYNAMIC_STRING ds_plugin_data_types; + +static char *opt_password= 0; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; + +static char *cnf_file_path= 0, defaults_file[FN_REFLEN + 32]; + +static my_bool tty_password= 0; + +static char opt_tmpdir[FN_REFLEN] = ""; + +#ifndef DBUG_OFF +static char *default_dbug_option= (char*) "d:t:O,/tmp/mariadb-upgrade.trace"; +#endif + +static char **defaults_argv; + +static my_bool not_used; /* Can't use GET_BOOL without a value pointer */ + +char upgrade_from_version[1024]; + +static my_bool opt_write_binlog; + +static void print_conn_args(const char *tool_name); + +#define OPT_SILENT OPT_MAX_CLIENT_OPTION + +static struct my_option my_long_options[]= +{ + {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"basedir", 'b', + "Not used by mysql_upgrade. Only for backward compatibility.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Not used by mysql_upgrade. Only for backward compatibility.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, + {"compress", OPT_COMPRESS, + "Not used by mysql_upgrade. Only for backward compatibility.", + ¬_used, ¬_used, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"datadir", 'd', + "Not used by mysql_upgrade. Only for backward compatibility.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag, + &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Not used by mysql_upgrade. Only for backward compatibility.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"check-if-upgrade-is-needed", OPT_CHECK_IF_UPGRADE_NEEDED, + "Exits with status 0 if an upgrades is required, 1 otherwise.", + &opt_check_upgrade, &opt_check_upgrade, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', "Force execution of mysqlcheck even if mysql_upgrade " + "has already been executed for the current version of MariaDB.", + &opt_not_used, &opt_not_used, 0 , GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", 0, + 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given," + " it's solicited on the tty.", &opt_password,&opt_password, + 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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) ").", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", OPT_SILENT, "Print less information", &opt_silent, + &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include + {"tmpdir", 't', "Directory for temporary files.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"upgrade-system-tables", 's', "Only upgrade the system tables in the mysql database. Tables in other databases are not checked or touched.", + &opt_systables_only, &opt_systables_only, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"user", 'u', "User for login.", &opt_user, + &opt_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"verbose", 'v', "Display more output about the process; Using it twice will print connection argument;" + "Using it 3 times will print out all CHECK, RENAME and ALTER TABLE during the check phase;" + "Using it 4 times (added in MariaDB 10.0.14) will also write out all mariadb-check commands used;" + "Using it 5 times will print all the mariadb commands used and their results while running mysql_fix_privilege_tables script.", + &opt_not_used, &opt_not_used, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version-check", 'k', + "Run this program only if its \'server version\' " + "matches the version of the server to which it's connecting. " + "Note: the \'server version\' of the program is the version of the MariaDB " + "server with which it was built/distributed.", + &opt_version_check, &opt_version_check, 0, + GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"write-binlog", OPT_WRITE_BINLOG, "All commands including those " + "issued by mysqlcheck are written to the binary log.", + &opt_write_binlog, &opt_write_binlog, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static const char *load_default_groups[]= +{ + "client", /* Read settings how to connect to server */ + "mysql_upgrade", /* Read special settings for mysql_upgrade */ + "mariadb-upgrade", /* Read special settings for mysql_upgrade */ + "client-server", /* Reads settings common between client & server */ + "client-mariadb", /* Read mariadb unique client settings */ + 0 +}; + +static void free_used_memory(void) +{ + /* Free memory allocated by 'load_defaults' */ + if (defaults_argv) + free_defaults(defaults_argv); + + dynstr_free(&ds_args); + dynstr_free(&conn_args); + dynstr_free(&ds_plugin_data_types); + if (cnf_file_path) + my_delete(cnf_file_path, MYF(MY_WME)); + if (info_file >= 0) + { + (void) my_lock(info_file, F_UNLCK, 0, 1, MYF(0)); + my_close(info_file, MYF(MY_WME)); + info_file= -1; + } +} + + +static void die(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("die"); + + /* Print the error message */ + print_conn_args("mariadb-check"); + fflush(stdout); + va_start(args, fmt); + if (fmt) + { + fprintf(stderr, "FATAL ERROR: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + va_end(args); + + free_used_memory(); + my_end(my_end_arg); + exit(1); +} + + +static void verbose(const char *fmt, ...) +{ + va_list args; + + if (opt_silent) + return; + + /* Print the verbose message */ + va_start(args, fmt); + if (fmt) + { + vfprintf(stdout, fmt, args); + fprintf(stdout, "\n"); + fflush(stdout); + } + va_end(args); +} + + +static void print_error(const char *error_msg, DYNAMIC_STRING *output) +{ + fprintf(stderr, "%s\n", error_msg); + fprintf(stderr, "%s", output->str); +} + + +/* + Add one option - passed to mysql_upgrade on command line + or by defaults file(my.cnf) - to a dynamic string, in + this way we pass the same arguments on to mysql and mysql_check +*/ + +static void add_one_option_cmd_line(DYNAMIC_STRING *ds, + const char *name, + const char *arg) +{ + dynstr_append(ds, "--"); + dynstr_append(ds, name); + if (arg) + { + dynstr_append(ds, "="); + dynstr_append_os_quoted(ds, arg, NullS); + } + dynstr_append(ds, " "); +} + +static void add_one_option_cnf_file(DYNAMIC_STRING *ds, + const char *name, + const char *arg) +{ + dynstr_append(ds, name); + if (arg) + { + dynstr_append(ds, "="); + dynstr_append_os_quoted(ds, arg, NullS); + } + dynstr_append(ds, "\n"); +} + + +static my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename __attribute__((unused))) +{ + my_bool add_option= TRUE; + + switch (opt->id) { + + case '?': + printf("%s Ver %s Distrib %s, for %s (%s)\n", + my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + puts("MariaDB utility for upgrading databases to new MariaDB versions."); + print_defaults("my", load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); + die(0); + break; + + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + add_option= FALSE; + debug_check_flag= 1; + break; + + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + add_option= FALSE; + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + /* Add password to ds_args before overwriting the arg with x's */ + add_one_option_cnf_file(&ds_args, opt->name, argument); + while (*argument) + *(char*)argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]= 0; + tty_password= 0; + } + else + tty_password= 1; + break; + + case 't': + strnmov(opt_tmpdir, argument, sizeof(opt_tmpdir)); + add_option= FALSE; + break; + + case 'b': /* --basedir */ + case 'd': /* --datadir */ + fprintf(stderr, "%s: the '--%s' option is always ignored\n", + my_progname, opt->id == 'b' ? "basedir" : "datadir"); + /* FALLTHROUGH */ + + case 'k': /* --version-check */ + case 'v': /* --verbose */ + opt_verbose++; + if (argument == disabled_my_option) + { + opt_verbose= 0; + opt_silent= 1; + } + add_option= 0; + break; + case 'V': + printf("%s Ver %s Distrib %s, for %s (%s)\n", + my_progname, VER, MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); + die(0); + break; + case 'f': /* --force */ + opt_force++; + if (argument == disabled_my_option) + opt_force= 0; + add_option= 0; + break; + case OPT_SILENT: + opt_verbose= 0; + add_option= 0; + break; + case OPT_CHECK_IF_UPGRADE_NEEDED: /* --check-if-upgrade-needed */ + case 's': /* --upgrade-system-tables */ + case OPT_WRITE_BINLOG: /* --write-binlog */ + add_option= FALSE; + break; + + case 'h': /* --host */ + case 'W': /* --pipe */ + case 'P': /* --port */ + case 'S': /* --socket */ + case OPT_MYSQL_PROTOCOL: /* --protocol */ + case OPT_PLUGIN_DIR: /* --plugin-dir */ + case OPT_DEFAULT_AUTH: /* --default-auth */ + add_one_option_cmd_line(&conn_args, opt->name, argument); + break; + } + + if (add_option) + { + /* + This is an option that is accepted by mysql_upgrade just so + it can be passed on to "mysql" and "mysqlcheck" + Save it in the ds_args string + */ + add_one_option_cnf_file(&ds_args, opt->name, argument); + } + return 0; +} + + +/* Convert the specified version string into the numeric format. */ + +static ulong STDCALL calc_server_version(char *some_version) +{ + uint major, minor, version; + char *point= some_version, *end_point; + major= (uint) strtoul(point, &end_point, 10); point=end_point+1; + minor= (uint) strtoul(point, &end_point, 10); point=end_point+1; + version= (uint) strtoul(point, &end_point, 10); + return (ulong) major * 10000L + (ulong)(minor * 100 + version); +} + +/** + Run a command using the shell, storing its output in the supplied dynamic + string. +*/ +static int run_command(char* cmd, + DYNAMIC_STRING *ds_res) +{ + char buf[512]= {0}; + FILE *res_file; + int error; + + if (opt_verbose >= 4) + puts(cmd); + + if (!(res_file= my_popen(cmd, "r"))) + die("popen(\"%s\", \"r\") failed", cmd); + + while (fgets(buf, sizeof(buf), res_file)) + { +#ifdef _WIN32 + /* Strip '\r' off newlines. */ + size_t len = strlen(buf); + if (len > 1 && buf[len - 2] == '\r' && buf[len - 1] == '\n') + { + buf[len - 2] = '\n'; + buf[len - 1] = 0; + } +#endif + DBUG_PRINT("info", ("buf: %s", buf)); + if(ds_res) + { + /* Save the output of this command in the supplied string */ + dynstr_append(ds_res, buf); + } + else + { + /* Print it directly on screen */ + fprintf(stdout, "%s", buf); + } + } + + error= my_pclose(res_file); + return WEXITSTATUS(error); +} + + +static int run_tool(char *tool_path, DYNAMIC_STRING *ds_res, ...) +{ + int ret; + const char* arg; + va_list args; + DYNAMIC_STRING ds_cmdline; + + DBUG_ENTER("run_tool"); + DBUG_PRINT("enter", ("tool_path: %s", tool_path)); + + if (init_dynamic_string(&ds_cmdline, IF_WIN("\"", ""), FN_REFLEN, FN_REFLEN)) + die("Out of memory"); + + dynstr_append_os_quoted(&ds_cmdline, tool_path, NullS); + dynstr_append(&ds_cmdline, " "); + + va_start(args, ds_res); + + while ((arg= va_arg(args, char *))) + { + /* Options should already be os quoted */ + dynstr_append(&ds_cmdline, arg); + dynstr_append(&ds_cmdline, " "); + } + + va_end(args); + +#ifdef _WIN32 + dynstr_append(&ds_cmdline, "\""); +#endif + + DBUG_PRINT("info", ("Running: %s", ds_cmdline.str)); + ret= run_command(ds_cmdline.str, ds_res); + DBUG_PRINT("exit", ("ret: %d", ret)); + dynstr_free(&ds_cmdline); + DBUG_RETURN(ret); +} + + +/** + Look for the filename of given tool, with the presumption that it is in the + same directory as mysql_upgrade and that the same executable-searching + mechanism will be used when we run our sub-shells with popen() later. +*/ +static void find_tool(char *tool_executable_name, const char *tool_name, + const char *self_name) +{ + char *last_fn_libchar; + DYNAMIC_STRING ds_tmp; + DBUG_ENTER("find_tool"); + DBUG_PRINT("enter", ("progname: %s", my_progname)); + + if (init_dynamic_string(&ds_tmp, "", 32, 32)) + die("Out of memory"); + + last_fn_libchar= strrchr(self_name, FN_LIBCHAR); + + if (last_fn_libchar == NULL) + { + /* + mysql_upgrade was found by the shell searching the path. A sibling + next to us should be found the same way. + */ + strncpy(tool_executable_name, tool_name, FN_REFLEN); + } + else + { + int len; + + /* + mysql_upgrade was run absolutely or relatively. We can find a sibling + by replacing our name after the LIBCHAR with the new tool name. + */ + + /* + When running in a not yet installed build and using libtool, + the program(mysql_upgrade) will be in .libs/ and executed + through a libtool wrapper in order to use the dynamic libraries + from this build. The same must be done for the tools(mysql and + mysqlcheck). Thus if path ends in .libs/, step up one directory + and execute the tools from there + */ + if (((last_fn_libchar - 6) >= self_name) && + (strncmp(last_fn_libchar - 5, ".libs", 5) == 0) && + (*(last_fn_libchar - 6) == FN_LIBCHAR)) + { + DBUG_PRINT("info", ("Chopping off \".libs\" from end of path")); + last_fn_libchar -= 6; + } + + len= (int)(last_fn_libchar - self_name); + + my_snprintf(tool_executable_name, FN_REFLEN, "%.*b%c%s", + len, self_name, FN_LIBCHAR, tool_name); + } + + if (opt_verbose) + verbose("Looking for '%s' as: %s", tool_name, tool_executable_name); + + /* + Make sure it can be executed + */ + if (run_tool(tool_executable_name, + &ds_tmp, /* Get output from command, discard*/ + "--no-defaults", + "--help", + "2>&1", + IF_WIN("> NUL", "> /dev/null"), + NULL)) + die("Can't execute '%s'", tool_executable_name); + + dynstr_free(&ds_tmp); + + DBUG_VOID_RETURN; +} + + +/* + Run query using "mysql" +*/ + +static int run_query(const char *query, DYNAMIC_STRING *ds_res, + my_bool force) +{ + int ret; + File fd; + char query_file_path[FN_REFLEN]; +#ifdef WITH_WSREP + /* + Strictly speaking, WITH_WSREP on the client only means that the + client was compiled with WSREP, it doesn't mean the server was, + so the server might not have WSREP_ON variable. + + But mysql_upgrade is tightly bound to a specific server version + anyway - it was mysql_fix_privilege_tables_sql script embedded + into its binary - so even if it won't assume anything about server + wsrep-ness, it won't be any less server-dependent. + */ + const uchar sql_log_bin[]= "SET SQL_LOG_BIN=0, WSREP_ON=OFF;"; +#else + const uchar sql_log_bin[]= "SET SQL_LOG_BIN=0;"; +#endif /* WITH_WSREP */ + + DBUG_ENTER("run_query"); + DBUG_PRINT("enter", ("query: %s", query)); + if ((fd= create_temp_file(query_file_path, + opt_tmpdir[0] ? opt_tmpdir : NULL, + "sql", O_SHARE, MYF(MY_WME))) < 0) + die("Failed to create temporary file for defaults"); + + /* + Master and slave should be upgraded separately. All statements executed + by mysql_upgrade will not be binlogged. + 'SET SQL_LOG_BIN=0' is executed before any other statements. + */ + if (!opt_write_binlog) + { + if (my_write(fd, sql_log_bin, sizeof(sql_log_bin)-1, + MYF(MY_FNABP | MY_WME))) + { + my_close(fd, MYF(MY_WME)); + my_delete(query_file_path, MYF(0)); + die("Failed to write to '%s'", query_file_path); + } + } + + if (my_write(fd, (uchar*) query, strlen(query), + MYF(MY_FNABP | MY_WME))) + { + my_close(fd, MYF(MY_WME)); + my_delete(query_file_path, MYF(0)); + die("Failed to write to '%s'", query_file_path); + } + + ret= run_tool(mysql_path, + ds_res, + defaults_file, + "--database=mysql", + "--batch", /* Turns off pager etc. */ + force ? "--force": "--skip-force", + opt_verbose >= 5 ? "--verbose" : "", + ds_res || opt_silent ? "--silent": "", + "<", + query_file_path, + "2>&1", + NULL); + + my_close(fd, MYF(MY_WME)); + my_delete(query_file_path, MYF(0)); + + DBUG_RETURN(ret); +} + + +/* + Extract the value returned from result of "show variable like ..." +*/ + +static int extract_variable_from_show(DYNAMIC_STRING* ds, char* value) +{ + char *value_start, *value_end; + size_t len; + + /* + The query returns "datadir\t\n", skip past + the tab + */ + if ((value_start= strchr(ds->str, '\t')) == NULL) + return 1; /* Unexpected result */ + value_start++; + + /* Don't copy the ending newline */ + if ((value_end= strchr(value_start, '\n')) == NULL) + return 1; /* Unexpected result */ + + len= (size_t) MY_MIN(FN_REFLEN, value_end-value_start); + strncpy(value, value_start, len); + value[len]= '\0'; + return 0; +} + + +static int get_upgrade_info_file_name(char* name) +{ + DYNAMIC_STRING ds_datadir; + DBUG_ENTER("get_upgrade_info_file_name"); + + if (init_dynamic_string(&ds_datadir, NULL, 32, 32)) + die("Out of memory"); + + if (run_query("show variables like 'datadir'", + &ds_datadir, FALSE) || + extract_variable_from_show(&ds_datadir, name)) + { + print_error("Reading datadir from the MariaDB server failed. Got the " + "following error when executing the 'mysql' command line client", + &ds_datadir); + dynstr_free(&ds_datadir); + DBUG_RETURN(1); /* Query failed */ + } + + dynstr_free(&ds_datadir); + + fn_format(name, "mysql_upgrade_info", name, "", MYF(0)); + DBUG_PRINT("exit", ("name: %s", name)); + DBUG_RETURN(0); +} + +static char upgrade_info_file[FN_REFLEN]= {0}; + + +/* + Open or create mysql_upgrade_info file in servers data dir. + + Take a lock to ensure there cannot be any other mysql_upgrades + running concurrently +*/ + +const char *create_error_message= + "%sCould not open or create the upgrade info file '%s' in " + "the MariaDB Servers data directory, errno: %d (%s)\n"; + + + +static void open_mysql_upgrade_file() +{ + char errbuff[80]; + if (get_upgrade_info_file_name(upgrade_info_file)) + { + die("Upgrade failed"); + } + if ((info_file= my_create(upgrade_info_file, 0, + O_RDWR | O_NOFOLLOW, + MYF(0))) < 0) + { + if (opt_force >= 2) + { + fprintf(stdout, create_error_message, + "", upgrade_info_file, errno, + my_strerror(errbuff, sizeof(errbuff)-1, errno)); + fprintf(stdout, + "--force --force used, continuing without using the %s file.\n" + "Note that this means that there is no protection against " + "concurrent mysql_upgrade executions and next mysql_upgrade run " + "will do a full upgrade again!\n", + upgrade_info_file); + return; + } + fprintf(stdout, create_error_message, + "FATAL ERROR: ", + upgrade_info_file, errno, + my_strerror(errbuff, sizeof(errbuff)-1, errno)); + if (errno == EACCES) + { + fprintf(stderr, + "Note that mysql_upgrade should be run as the same user as the " + "MariaDB server binary, normally 'mysql' or 'root'.\n" + "Alternatively you can use mysql_upgrade --force --force. " + "Please check the documentation if you decide to use the force " + "option!\n"); + } + fflush(stderr); + die(0); + } + if (my_lock(info_file, F_WRLCK, 0, 1, MYF(0))) + { + die("Could not exclusively lock on file '%s'. Error %d: %s\n", + upgrade_info_file, my_errno, + my_strerror(errbuff, sizeof(errbuff)-1, my_errno)); + } +} + + +/** + Place holder for versions that require a major upgrade + + @return 0 upgrade has already been run on this version + @return 1 upgrade has to be run + +*/ + +static int faulty_server_versions(const char *version) +{ + return 0; +} + +/* + Read the content of mysql_upgrade_info file and + compare the version number form file against + version number which mysql_upgrade was compiled for + + NOTE + This is an optimization to avoid running mysql_upgrade + when it's already been performed for the particular + version of MariaDB. + + In case the MariaDBL server can't return the upgrade info + file it's always better to report that the upgrade hasn't + been performed. + + @return 0 Upgrade has already been run on this version + @return > 0 Upgrade has to be run +*/ + +static int upgrade_already_done(int silent) +{ + const char *version = MYSQL_SERVER_VERSION; + const char *s; + char *pos; + my_off_t length; + + if (info_file < 0) + { + DBUG_ASSERT(opt_force > 1); + return 1; /* No info file and --force */ + } + + bzero(upgrade_from_version, sizeof(upgrade_from_version)); + + (void) my_seek(info_file, 0, SEEK_SET, MYF(0)); + /* We have -3 here to make calc_server_version() safe */ + length= my_read(info_file, (uchar*) upgrade_from_version, + sizeof(upgrade_from_version)-3, + MYF(MY_WME)); + + if (!length) + { + if (opt_verbose) + verbose("Empty or non existent %s. Assuming mysql_upgrade has to be run!", + upgrade_info_file); + return 1; + } + + /* Remove possible \Å‹ that may end in output */ + if ((pos= strchr(upgrade_from_version, '\n'))) + *pos= 0; + + if (faulty_server_versions(upgrade_from_version)) + { + if (opt_verbose) + verbose("Upgrading from version %s requires mysql_upgrade to be run!", + upgrade_from_version); + return 2; + } + + s= strchr(version, '.'); + s= strchr(s + 1, '.'); + + if (strncmp(upgrade_from_version, version, + (size_t)(s - version + 1))) + { + if (calc_server_version(upgrade_from_version) <= MYSQL_VERSION_ID) + { + verbose("Major version upgrade detected from %s to %s. Check required!", + upgrade_from_version, version); + return 3; + } + die("Version mismatch (%s -> %s): Trying to downgrade from a higher to " + "lower version is not supported!", + upgrade_from_version, version); + } + if (!silent) + { + verbose("This installation of MariaDB is already upgraded to %s.\n" + "There is no need to run mysql_upgrade again for %s.", + upgrade_from_version, version); + if (!opt_check_upgrade) + verbose("You can use --force if you still want to run mysql_upgrade"); + } + return 0; +} + +static void finish_mysql_upgrade_info_file(void) +{ + if (info_file < 0) + return; + + /* Write new version to file */ + (void) my_seek(info_file, 0, SEEK_CUR, MYF(0)); + (void) my_chsize(info_file, 0, 0, MYF(0)); + (void) my_seek(info_file, 0, 0, MYF(0)); + (void) my_write(info_file, (uchar*) MYSQL_SERVER_VERSION, + sizeof(MYSQL_SERVER_VERSION)-1, MYF(MY_WME)); + (void) my_write(info_file, (uchar*) "\n", 1, MYF(MY_WME)); + (void) my_lock(info_file, F_UNLCK, 0, 1, MYF(0)); + + /* + Check if the upgrade_info_file was properly created/updated + It's not a fatal error -> just print a message if it fails + */ + if (upgrade_already_done(1)) + fprintf(stderr, + "Could not write to the upgrade info file '%s' in " + "the MariaDB Servers datadir, errno: %d\n", + upgrade_info_file, errno); + + my_close(info_file, MYF(MY_WME)); + info_file= -1; + return; +} + + +/* + Print connection-related arguments. +*/ + +static void print_conn_args(const char *tool_name) +{ + if (opt_verbose < 2) + return; + if (conn_args.str[0]) + verbose("Running '%s' with connection arguments: %s", tool_name, + conn_args.str); + else + verbose("Running '%s with default connection arguments", tool_name); +} + +/* + Check and upgrade(if necessary) all tables + in the server using "mysqlcheck --check-upgrade .." +*/ + +static int run_mysqlcheck_upgrade(my_bool mysql_db_only) +{ + const char *what= mysql_db_only ? "mysql database" : "tables"; + const char *arg1= mysql_db_only ? "--databases" : "--all-databases"; + const char *arg2= mysql_db_only ? "mysql" : "--skip-database=mysql"; + int retch; + if (opt_systables_only && !mysql_db_only) + { + verbose("Phase %d/%d: Checking and upgrading %s... Skipped", + ++phase, phases_total, what); + return 0; + } + verbose("Phase %d/%d: Checking and upgrading %s", ++phase, phases_total, what); + print_conn_args("mariadb-check"); + retch= run_tool(mysqlcheck_path, + NULL, /* Send output from mysqlcheck directly to screen */ + defaults_file, + "--check-upgrade", + "--auto-repair", + !opt_silent || opt_verbose >= 1 ? "--verbose" : "", + opt_verbose >= 2 ? "--verbose" : "", + opt_verbose >= 3 ? "--verbose" : "", + opt_silent ? "--silent": "", + opt_write_binlog ? "--write-binlog" : "--skip-write-binlog", + arg1, arg2, + "2>&1", + NULL); + return retch; +} + +#define EVENTS_STRUCT_LEN 7000 + +static my_bool is_mysql() +{ + my_bool ret= TRUE; + DYNAMIC_STRING ds_events_struct; + + if (init_dynamic_string(&ds_events_struct, NULL, + EVENTS_STRUCT_LEN, EVENTS_STRUCT_LEN)) + die("Out of memory"); + + if (run_query("show create table mysql.event", + &ds_events_struct, FALSE) || + strstr(ds_events_struct.str, "IGNORE_BAD_TABLE_OPTIONS") != NULL) + ret= FALSE; + else + verbose("MariaDB upgrade detected"); + + dynstr_free(&ds_events_struct); + return(ret); +} + +static int run_mysqlcheck_views(void) +{ + const char *upgrade_views="--process-views=UPGRADE"; + if (upgrade_from_mysql) + { + /* + this has to ignore opt_systables_only, because upgrade_from_mysql + is determined by analyzing systables. if we honor opt_systables_only + here, views won't be fixed by subsequent mysql_upgrade runs + */ + upgrade_views="--process-views=UPGRADE_FROM_MYSQL"; + verbose("Phase %d/%d: Fixing views from mysql", ++phase, phases_total); + } + else if (opt_systables_only) + { + verbose("Phase %d/%d: Fixing views... Skipped", ++phase, phases_total); + return 0; + } + else + verbose("Phase %d/%d: Fixing views", ++phase, phases_total); + + print_conn_args("mysqlcheck"); + return run_tool(mysqlcheck_path, + NULL, /* Send output from mysqlcheck directly to screen */ + defaults_file, + "--all-databases", "--repair", + upgrade_views, + "--skip-process-tables", + opt_verbose ? "--verbose": "", + opt_silent ? "--silent": "", + opt_write_binlog ? "--write-binlog" : "--skip-write-binlog", + "2>&1", + NULL); +} + +static int run_mysqlcheck_fixnames(void) +{ + if (opt_systables_only) + { + verbose("Phase %d/%d: Fixing table and database names ... Skipped", + ++phase, phases_total); + return 0; + } + verbose("Phase %d/%d: Fixing table and database names", + ++phase, phases_total); + print_conn_args("mysqlcheck"); + return run_tool(mysqlcheck_path, + NULL, /* Send output from mysqlcheck directly to screen */ + defaults_file, + "--all-databases", + "--fix-db-names", + "--fix-table-names", + opt_verbose >= 1 ? "--verbose" : "", + opt_verbose >= 2 ? "--verbose" : "", + opt_verbose >= 3 ? "--verbose" : "", + opt_silent ? "--silent": "", + opt_write_binlog ? "--write-binlog" : "--skip-write-binlog", + "2>&1", + NULL); +} + + +static const char *expected_errors[]= +{ + "ERROR 1051", /* Unknown table */ + "ERROR 1060", /* Duplicate column name */ + "ERROR 1061", /* Duplicate key name */ + "ERROR 1054", /* Unknown column */ + "ERROR 1146", /* Table does not exist */ + "ERROR 1290", /* RR_OPTION_PREVENTS_STATEMENT */ + "ERROR 1347", /* 'mysql.user' is not of type 'BASE TABLE' */ + "ERROR 1348", /* Column 'Show_db_priv' is not updatable */ + "ERROR 1356", /* definer of view lack rights (UPDATE) */ + "ERROR 1449", /* definer ('mariadb.sys'@'localhost') of mysql.user does not exist */ + 0 +}; + + +static my_bool is_expected_error(const char* line) +{ + const char** error= expected_errors; + while (*error) + { + /* + Check if lines starting with ERROR + are in the list of expected errors + */ + if (strncmp(line, "ERROR", 5) != 0 || + strncmp(line, *error, strlen(*error)) == 0) + return 1; /* Found expected error */ + error++; + } + return 0; +} + + +static char* get_line(char* line) +{ + while (*line && *line != '\n') + line++; + if (*line) + line++; + return line; +} + + +/* Print the current line to stderr */ +static void print_line(char* line) +{ + while (*line && *line != '\n') + { + fputc(*line, stderr); + line++; + } + fputc('\n', stderr); +} + +static my_bool from_before_10_1() +{ + my_bool ret= TRUE; + DYNAMIC_STRING ds_events_struct; + + if (upgrade_from_version[0]) + { + return upgrade_from_version[1] == '.' || + strncmp(upgrade_from_version, "10.1.", 5) < 0; + } + + if (init_dynamic_string(&ds_events_struct, NULL, 2048, 2048)) + die("Out of memory"); + + if (run_query("show create table mysql.user", &ds_events_struct, FALSE) || + strstr(ds_events_struct.str, "default_role") != NULL) + ret= FALSE; + else + verbose("Upgrading from a version before MariaDB-10.1"); + + dynstr_free(&ds_events_struct); + return ret; +} + + +static int uninstall_plugins(void) +{ + verbose("Phase %d/%d: uninstalling plugins", ++phase, phases_total); + if (ds_plugin_data_types.length) + { + char *plugins= ds_plugin_data_types.str; + char *next= get_line(plugins); + char buff[512]; + while(*plugins) + { + if (next[-1] == '\n') + next[-1]= 0; + verbose("uninstalling plugin for %s data type", plugins); + strxnmov(buff, sizeof(buff)-1, "UNINSTALL SONAME ", plugins,"", NULL); + run_query(buff, NULL, TRUE); + plugins= next; + next= get_line(next); + } + } + return 0; +} + + +/** + @brief Install plugins for missing data types + @details Check for entries with "Unknown data type" in I_S.TABLES, + try to load plugins for these tables if available (MDEV-24093) + + @return Operation status + @retval TRUE - error + @retval FALSE - success +*/ +static int install_used_plugin_data_types(void) +{ + DYNAMIC_STRING ds_result; + const char *query = "SELECT table_comment FROM information_schema.tables" + " WHERE table_comment LIKE 'Unknown data type: %'"; + if (init_dynamic_string(&ds_result, "", 512, 512)) + die("Out of memory"); + run_query(query, &ds_result, TRUE); + + if (ds_result.length) + { + char *line= ds_result.str; + char *next= get_line(line); + while(*line) + { + if (next[-1] == '\n') + next[-1]= 0; + if (strstr(line, "'MYSQL_JSON'")) + { + verbose("installing plugin for MYSQL_JSON data type"); + if(!run_query("INSTALL SONAME 'type_mysql_json'", NULL, TRUE)) + { + dynstr_append(&ds_plugin_data_types, "'type_mysql_json'"); + dynstr_append(&ds_plugin_data_types, "\n"); + break; + } + else + { + fprintf(stderr, "... can't %s\n", "INSTALL SONAME 'type_mysql_json'"); + return 1; + } + } + line= next; + next= get_line(next); + } + } + dynstr_free(&ds_result); + return 0; +} + + +/* + Check for entries with "Unknown storage engine" in I_S.TABLES, + try to load plugins for these tables if available (MDEV-11942) +*/ +static int install_used_engines(void) +{ + char buf[512]; + DYNAMIC_STRING ds_result; + const char *query = "SELECT DISTINCT LOWER(engine) AS c1 FROM information_schema.tables" + " WHERE table_comment LIKE 'Unknown storage engine%'" + " ORDER BY c1"; + + if (opt_systables_only || !from_before_10_1()) + { + verbose("Phase %d/%d: Installing used storage engines... Skipped", ++phase, phases_total); + return 0; + } + verbose("Phase %d/%d: Installing used storage engines", ++phase, phases_total); + + if (init_dynamic_string(&ds_result, "", 512, 512)) + die("Out of memory"); + + verbose("Checking for tables with unknown storage engine"); + + run_query(query, &ds_result, TRUE); + + if (ds_result.length) + { + char *line= ds_result.str, *next=get_line(line); + do + { + if (next[-1] == '\n') + next[-1]=0; + + verbose("installing plugin for '%s' storage engine", line); + + // we simply assume soname=ha_enginename + strxnmov(buf, sizeof(buf)-1, "install soname 'ha_", line, "'", NULL); + + + if (run_query(buf, NULL, TRUE)) + fprintf(stderr, "... can't %s\n", buf); + line=next; + next=get_line(line); + } while (*line); + } + dynstr_free(&ds_result); + return 0; +} + + +static int check_slave_repositories(void) +{ + DYNAMIC_STRING ds_result; + int row_count= 0; + int error= 0; + const char *query = "SELECT COUNT(*) AS c1 FROM mysql.slave_master_info"; + + if (init_dynamic_string(&ds_result, "", 512, 512)) + die("Out of memory"); + + run_query(query, &ds_result, TRUE); + + if (ds_result.length) + { + row_count= atoi((char *)ds_result.str); + if (row_count) + { + fprintf(stderr,"Slave info repository compatibility check:" + " Found data in `mysql`.`slave_master_info` table.\n"); + fprintf(stderr,"Warning: Content of `mysql`.`slave_master_info` table" + " will be ignored as MariaDB supports file based info " + "repository.\n"); + error= 1; + } + } + dynstr_free(&ds_result); + + query = "SELECT COUNT(*) AS c1 FROM mysql.slave_relay_log_info"; + + if (init_dynamic_string(&ds_result, "", 512, 512)) + die("Out of memory"); + + run_query(query, &ds_result, TRUE); + + if (ds_result.length) + { + row_count= atoi((char *)ds_result.str); + if (row_count) + { + fprintf(stderr, "Slave info repository compatibility check:" + " Found data in `mysql`.`slave_relay_log_info` table.\n"); + fprintf(stderr, "Warning: Content of `mysql`.`slave_relay_log_info` " + "table will be ignored as MariaDB supports file based " + "repository.\n"); + error= 1; + } + } + dynstr_free(&ds_result); + if (error) + { + fprintf(stderr,"Slave server may not possess the correct replication " + "metadata.\n"); + fprintf(stderr, "Execution of CHANGE MASTER as per " + "`mysql`.`slave_master_info` and `mysql`.`slave_relay_log_info` " + "table content is recommended.\n"); + } + return 0; +} + +/* + Update all system tables in MariaDB Server to current + version using "mysql" to execute all the SQL commands + compiled into the mysql_fix_privilege_tables array +*/ + +static int run_sql_fix_privilege_tables(void) +{ + int found_real_errors= 0; + const char **query_ptr; + DYNAMIC_STRING ds_script; + DYNAMIC_STRING ds_result; + DBUG_ENTER("run_sql_fix_privilege_tables"); + + if (init_dynamic_string(&ds_script, "", 65536, 1024)) + die("Out of memory"); + + if (init_dynamic_string(&ds_result, "", 512, 512)) + die("Out of memory"); + + verbose("Phase %d/%d: Running 'mysql_fix_privilege_tables'", + ++phase, phases_total); + + /* + Individual queries can not be executed independently by invoking + a forked mysql client, because the script uses session variables + and prepared statements. + */ + for ( query_ptr= &mysql_fix_privilege_tables[0]; + *query_ptr != NULL; + query_ptr++ + ) + { + if (strcasecmp(*query_ptr, "flush privileges;\n")) + dynstr_append(&ds_script, *query_ptr); + } + + run_query(ds_script.str, (opt_verbose >= 5) ? NULL : &ds_result, TRUE); + + { + /* + Scan each line of the result for real errors + and ignore the expected one(s) like "Duplicate column name", + "Unknown column" and "Duplicate key name" since they just + indicate the system tables are already up to date + */ + char *line= ds_result.str; + do + { + if (!is_expected_error(line)) + { + /* Something unexpected failed, dump error line to screen */ + found_real_errors++; + print_line(line); + } + else if (strncmp(line, "WARNING", 7) == 0) + { + print_line(line); + } + } while ((line= get_line(line)) && *line); + } + + dynstr_free(&ds_result); + dynstr_free(&ds_script); + DBUG_RETURN(found_real_errors); +} + + +static int flush_privileges(void) +{ + verbose("Phase %d/%d: Running 'FLUSH PRIVILEGES'", ++phase, phases_total); + return run_query("FLUSH PRIVILEGES", NULL, TRUE); +} + + +/** + Check if the server version matches with the server version mysql_upgrade + was compiled with. + + @return 0 match successful + 1 failed +*/ +static int check_version_match(void) +{ + DYNAMIC_STRING ds_version; + char version_str[NAME_CHAR_LEN + 1]; + + if (init_dynamic_string(&ds_version, NULL, NAME_CHAR_LEN, NAME_CHAR_LEN)) + die("Out of memory"); + + if (run_query("show variables like 'version'", + &ds_version, FALSE) || + extract_variable_from_show(&ds_version, version_str)) + { + print_error("Version check failed. Got the following error when calling " + "the 'mysql' command line client", &ds_version); + dynstr_free(&ds_version); + return 1; /* Query failed */ + } + + dynstr_free(&ds_version); + + if (calc_server_version((char *) version_str) != MYSQL_VERSION_ID) + { + fprintf(stderr, "Error: Server version (%s)\n" + "does not match the version of the server (%s)\n" + "with which this program was built/distributed. You can\n" + "use --skip-version-check to skip this check.\n", + version_str, MYSQL_SERVER_VERSION); + return 1; + } + return 0; +} + + +int main(int argc, char **argv) +{ + char self_name[FN_REFLEN + 1]; + + MY_INIT(argv[0]); + DBUG_PROCESS(argv[0]); + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv= argv; /* Must be freed by 'free_defaults' */ + +#if defined(_WIN32) + if (GetModuleFileName(NULL, self_name, FN_REFLEN) == 0) +#endif + { + strmake_buf(self_name, argv[0]); + } + + if (init_dynamic_string(&ds_args, "", 512, 256) || + init_dynamic_string(&conn_args, "", 512, 256) || + init_dynamic_string(&ds_plugin_data_types, "", 512, 256)) + die("Out of memory"); + + if (handle_options(&argc, &argv, my_long_options, get_one_option)) + die(NULL); + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + if (tty_password) + { + opt_password= my_get_tty_password(NullS); + /* add password to defaults file */ + add_one_option_cnf_file(&ds_args, "password", opt_password); + } + /* add user to defaults file */ + add_one_option_cnf_file(&ds_args, "user", opt_user); + + cnf_file_path= strmov(defaults_file, "--defaults-file="); + { + int fd= create_temp_file(cnf_file_path, opt_tmpdir[0] ? opt_tmpdir : NULL, + "mysql_upgrade-", 0, MYF(MY_FAE)); + if (fd < 0) + die(NULL); + my_write(fd, USTRING_WITH_LEN( "[client]\n"), MYF(MY_FAE)); + my_write(fd, (uchar*)ds_args.str, ds_args.length, MYF(MY_FAE)); + my_close(fd, MYF(MY_WME)); + } + + /* Find mysql */ + find_tool(mysql_path, IF_WIN("mariadb.exe", "mariadb"), self_name); + + open_mysql_upgrade_file(); + + if (opt_check_upgrade) + exit(upgrade_already_done(0) == 0); + + /* Find mysqlcheck */ + find_tool(mysqlcheck_path, IF_WIN("mariadb-check.exe", "mariadb-check"), self_name); + + if (opt_systables_only && !opt_silent) + printf("The --upgrade-system-tables option was used, user tables won't be touched.\n"); + + /* + Read the mysql_upgrade_info file to check if mysql_upgrade + already has been run for this installation of MariaDB + */ + if (!opt_force && !upgrade_already_done(0)) + goto end; /* Upgrade already done */ + + if (opt_version_check && check_version_match()) + die("Upgrade failed"); + + upgrade_from_mysql= is_mysql(); + + /* + Run "mysqlcheck" and "mysql_fix_privilege_tables.sql" + */ + if (run_mysqlcheck_upgrade(TRUE) || + install_used_engines() || + install_used_plugin_data_types() || + run_sql_fix_privilege_tables() || + run_mysqlcheck_views() || + run_mysqlcheck_fixnames() || + run_mysqlcheck_upgrade(FALSE) || + check_slave_repositories() || + uninstall_plugins() || + flush_privileges()) + die("Upgrade failed" ); + verbose("OK"); + + /* Finish writing indicating upgrade has been performed */ + finish_mysql_upgrade_info_file(); + + DBUG_ASSERT(phase == phases_total); + +end: + print_conn_args("mariadb-check"); + free_used_memory(); + my_end(my_end_arg); + exit(0); +} diff --git a/client/mysqladmin.cc b/client/mysqladmin.cc new file mode 100644 index 00000000..022ba2ae --- /dev/null +++ b/client/mysqladmin.cc @@ -0,0 +1,1712 @@ +/* + Copyright (c) 2000, 2014, Oracle and/or its affiliates. + Copyright (c) 2010, 2019, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA */ + +/* maintenance of mysql databases */ + +#include "client_priv.h" +#include +#include /* because of signal() */ +#include +#include +#include +#include +#include +#include +#include + +#define ADMIN_VERSION "9.1" +#define MAX_MYSQL_VAR 512 +#define SHUTDOWN_DEF_TIMEOUT 3600 /* Wait for shutdown */ +#define MAX_TRUNC_LENGTH 3 + +char *host= NULL, *user= 0, *opt_password= 0, + *default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME; +char truncated_var_names[MAX_MYSQL_VAR+100][MAX_TRUNC_LENGTH]; +char ex_var_names[MAX_MYSQL_VAR+100][FN_REFLEN]; +ulonglong last_values[MAX_MYSQL_VAR+100]; +static int interval=0; +static my_bool option_force=0,interrupted=0,new_line=0, + opt_compress= 0, opt_local= 0, opt_relative= 0, opt_verbose= 0, + opt_vertical= 0, tty_password= 0, opt_nobeep, + opt_shutdown_wait_for_slaves= 0; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static uint tcp_port = 0, option_wait = 0, option_silent=0, nr_iterations; +static uint opt_count_iterations= 0, my_end_arg; +static ulong opt_connect_timeout, opt_shutdown_timeout; +static char * unix_port=0; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; +static bool sql_log_bin_off= false; + +static uint opt_protocol=0; +static myf error_flags; /* flags to pass to my_printf_error, like ME_BELL */ + +/* + When using extended-status relatively, ex_val_max_len is the estimated + maximum length for any relative value printed by extended-status. The + idea is to try to keep the length of output as short as possible. +*/ + +static uint ex_val_max_len[MAX_MYSQL_VAR]; +static my_bool ex_status_printed = 0; /* First output is not relative. */ +static uint ex_var_count, max_var_length, max_val_length; + +#include + +static void print_version(void); +static void usage(void); +extern "C" my_bool get_one_option(int optid, const struct my_option *opt, + const char *argument); +static my_bool sql_connect(MYSQL *mysql, uint wait); +static int execute_commands(MYSQL *mysql,int argc, char **argv); +static char **mask_password(int argc, char ***argv); +static int drop_db(MYSQL *mysql,const char *db); +extern "C" sig_handler endprog(int signal_number); +static void nice_time(ulong sec,char *buff); +static void print_header(MYSQL_RES *result); +static void print_top(MYSQL_RES *result); +static void print_row(MYSQL_RES *result,MYSQL_ROW cur, uint row); +static void print_relative_row(MYSQL_RES *result, MYSQL_ROW cur, uint row); +static void print_relative_row_vert(MYSQL_RES *result, MYSQL_ROW cur, uint row); +static void print_relative_header(); +static void print_relative_line(); +static void truncate_names(); +static my_bool get_pidfile(MYSQL *mysql, char *pidfile); +static my_bool wait_pidfile(char *pidfile, time_t last_modified, + struct stat *pidfile_status); +static void store_values(MYSQL_RES *result); + +/* + The order of commands must be the same as command_names, + except ADMIN_ERROR +*/ +enum commands { + ADMIN_ERROR, + ADMIN_CREATE, ADMIN_DROP, ADMIN_SHUTDOWN, + ADMIN_RELOAD, ADMIN_REFRESH, ADMIN_VER, + ADMIN_PROCESSLIST, ADMIN_STATUS, ADMIN_KILL, + ADMIN_DEBUG, ADMIN_VARIABLES, ADMIN_FLUSH_LOGS, + ADMIN_FLUSH_HOSTS, ADMIN_FLUSH_TABLES, ADMIN_PASSWORD, + ADMIN_PING, ADMIN_EXTENDED_STATUS, ADMIN_FLUSH_STATUS, + ADMIN_FLUSH_PRIVILEGES, ADMIN_START_SLAVE, ADMIN_STOP_SLAVE, + ADMIN_START_ALL_SLAVES, ADMIN_STOP_ALL_SLAVES, + ADMIN_FLUSH_THREADS, ADMIN_OLD_PASSWORD, ADMIN_FLUSH_BINARY_LOG, + ADMIN_FLUSH_ENGINE_LOG, ADMIN_FLUSH_ERROR_LOG, ADMIN_FLUSH_GENERAL_LOG, + ADMIN_FLUSH_RELAY_LOG, ADMIN_FLUSH_SLOW_LOG, + ADMIN_FLUSH_TABLE_STATISTICS, ADMIN_FLUSH_INDEX_STATISTICS, + ADMIN_FLUSH_USER_STATISTICS, ADMIN_FLUSH_CLIENT_STATISTICS, + ADMIN_FLUSH_USER_RESOURCES, + ADMIN_FLUSH_ALL_STATUS, ADMIN_FLUSH_ALL_STATISTICS, ADMIN_FLUSH_SSL +}; +static const char *command_names[]= { + "create", "drop", "shutdown", + "reload", "refresh", "version", + "processlist", "status", "kill", + "debug", "variables", "flush-logs", + "flush-hosts", "flush-tables", "password", + "ping", "extended-status", "flush-status", + "flush-privileges", "start-slave", "stop-slave", + "start-all-slaves", "stop-all-slaves", + "flush-threads", "old-password", "flush-binary-log", "flush-engine-log", + "flush-error-log", "flush-general-log", "flush-relay-log", "flush-slow-log", + "flush-table-statistics", "flush-index-statistics", + "flush-user-statistics", "flush-client-statistics", "flush-user-resources", + "flush-all-status", "flush-all-statistics", "flush-ssl", + NullS +}; + +static TYPELIB command_typelib= +{ array_elements(command_names)-1,"commands", command_names, NULL}; + +static struct my_option my_long_options[] = +{ + {"count", 'c', + "Number of iterations to make. This works with -i (--sleep) only.", + &nr_iterations, &nr_iterations, 0, GET_UINT, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', + "Don't ask for confirmation on drop database; with multiple commands, " + "continue even if an error occurs.", + &option_force, &option_force, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", &charsets_dir, + &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", &host, &host, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"local", 'l', "Local command, don't write to binlog.", + &opt_local, &opt_local, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"no-beep", 'b', "Turn off beep on error.", &opt_nobeep, + &opt_nobeep, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given it's asked from the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#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) ").", + &tcp_port, &tcp_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"relative", 'r', + "Show difference between current and previous values when used with -i. " + "Currently only works with extended-status.", + &opt_relative, &opt_relative, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"silent", 's', "Silently exit if one can't connect to server.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &unix_port, &unix_port, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"sleep", 'i', "Execute commands repeatedly with a sleep between.", + &interval, &interval, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, + 0, 0}, +#include +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", &user, + &user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', "Write more information.", &opt_verbose, + &opt_verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"vertical", 'E', + "Print output vertically. Is similar to --relative, but prints output vertically.", + &opt_vertical, &opt_vertical, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"wait", 'w', "Wait and retry if connection is down.", 0, 0, 0, GET_UINT, + OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"connect_timeout", OPT_CONNECT_TIMEOUT, "", &opt_connect_timeout, + &opt_connect_timeout, 0, GET_ULONG, REQUIRED_ARG, 3600*12, 0, + 3600*12, 0, 1, 0}, + {"shutdown_timeout", OPT_SHUTDOWN_TIMEOUT, "", &opt_shutdown_timeout, + &opt_shutdown_timeout, 0, GET_ULONG, REQUIRED_ARG, + SHUTDOWN_DEF_TIMEOUT, 0, 3600*12, 0, 1, 0}, + {"wait_for_all_slaves", OPT_SHUTDOWN_WAIT_FOR_SLAVES, + "Defers shutdown until after all binlogged events have been sent to " + "all connected slaves", &opt_shutdown_wait_for_slaves, + &opt_shutdown_wait_for_slaves, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static const char *load_default_groups[]= +{ "mysqladmin", "mariadb-admin", "client", "client-server", "client-mariadb", + 0 }; + +my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + switch(opt->id) { + case 'c': + opt_count_iterations= 1; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; // Don't require password + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password=my_strdup(PSI_NOT_INSTRUMENTED, argument,MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]=0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password=1; + break; + case 's': + option_silent++; + break; + case 'W': +#ifdef _WIN32 + opt_protocol = MYSQL_PROTOCOL_PIPE; +#endif + break; + case '#': + DBUG_PUSH(argument ? argument : "d:t:o,/tmp/mysqladmin.trace"); + break; +#include + case 'V': + print_version(); + exit(0); + break; + case 'w': + if (argument) + { + if ((option_wait=atoi(argument)) <= 0) + option_wait=1; + } + else + option_wait= ~(uint)0; + break; + case '?': + case 'I': /* Info */ + usage(); + exit(0); + case OPT_CHARSETS_DIR: +#if MYSQL_VERSION_ID > 32300 + charsets_dir = argument; +#endif + break; + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + } + return 0; +} + + +int main(int argc,char *argv[]) +{ + int error= 0, temp_argc; + MYSQL mysql; + char **commands, **save_argv, **temp_argv; + + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + save_argv = argv; /* Save for free_defaults */ + + if ((error=handle_options(&argc, &argv, my_long_options, get_one_option))) + goto err2; + temp_argv= mask_password(argc, &argv); + temp_argc= argc; + + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + if (argc == 0) + { + usage(); + exit(1); + } + commands = temp_argv; + if (tty_password) + opt_password = my_get_tty_password(NullS); + + (void) signal(SIGINT,endprog); /* Here if abort */ + (void) signal(SIGTERM,endprog); /* Here if abort */ + + sf_leaking_memory=0; /* from now on we cleanup properly */ + + mysql_init(&mysql); + if (opt_compress) + mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); + if (opt_connect_timeout) + { + uint tmp=opt_connect_timeout; + mysql_options(&mysql,MYSQL_OPT_CONNECT_TIMEOUT, (char*) &tmp); + } +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(&mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(&mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(&mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(&mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); + mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); + error_flags= (myf)(opt_nobeep ? 0 : ME_BELL); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(&mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(&mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqladmin"); + if (sql_connect(&mysql, option_wait)) + { + /* + We couldn't get an initial connection and will definitely exit. + The following just determines the exit-code we'll give. + */ + + unsigned int err= mysql_errno(&mysql); + if (err >= CR_MIN_ERROR && err <= CR_MAX_ERROR) + error= 1; + else + { + /* Return 0 if all commands are PING */ + for (; argc > 0; argv++, argc--) + { + if (find_type(argv[0], &command_typelib, FIND_TYPE_BASIC) != + ADMIN_PING) + { + error= 1; + break; + } + } + } + } + else + { + /* + --count=0 aborts right here. Otherwise iff --sleep=t ("interval") + is given a t!=0, we get an endless loop, or n iterations if --count=n + was given an n!=0. If --sleep wasn't given, we get one iteration. + + To wit, --wait loops the connection-attempts, while --sleep loops + the command execution (endlessly if no --count is given). + */ + + while (!interrupted && (!opt_count_iterations || nr_iterations)) + { + new_line = 0; + + if ((error= execute_commands(&mysql,argc,commands))) + { + /* + Unknown/malformed command always aborts and can't be --forced. + If the user got confused about the syntax, proceeding would be + dangerous ... + */ + if (error > 0) + break; + + error= -error; /* don't exit with negative error codes */ + /* + Command was well-formed, but failed on the server. Might succeed + on retry (if conditions on server change etc.), but needs --force + to retry. + */ + if (!option_force) + break; + } /* if((error= ... */ + + if (interval) /* --sleep=interval given */ + { + if (opt_count_iterations && --nr_iterations == 0) + break; + + /* + If connection was dropped (unintentionally, or due to SHUTDOWN), + re-establish it if --wait ("retry-connect") was given and user + didn't signal for us to die. Otherwise, signal failure. + */ + + if (mysql.net.pvio == 0) + { + if (option_wait && !interrupted) + { + sleep(1); + sql_connect(&mysql, option_wait); + /* + continue normally and decrease counters so that + "mysqladmin --count=1 --wait=1 shutdown" + cannot loop endlessly. + */ + } + else + { + /* + connexion broke, and we have no order to re-establish it. fail. + */ + if (!option_force) + error= 1; + break; + } + } /* lost connection */ + + sleep(interval); + if (new_line) + puts(""); + } + else + break; /* no --sleep, done looping */ + } /* command-loop */ + } /* got connection */ + + mysql_close(&mysql); + temp_argc--; + while(temp_argc >= 0) + { + my_free(temp_argv[temp_argc]); + temp_argc--; + } + my_free(temp_argv); +err2: + mysql_library_end(); + my_free(opt_password); + my_free(user); + free_defaults(save_argv); + my_end(my_end_arg); + return error; +} + + +sig_handler endprog(int signal_number __attribute__((unused))) +{ + interrupted=1; +} + +/** + @brief connect to server, optionally waiting for same to come up + + @param mysql connection struct + @param wait wait for server to come up? + (0: no, ~0: forever, n: cycles) + + @return Operation result + @retval 0 success + @retval 1 failure +*/ + +static my_bool sql_connect(MYSQL *mysql, uint wait) +{ + my_bool info=0; + + for (;;) + { + if (mysql_real_connect(mysql,host,user,opt_password,NullS,tcp_port, + unix_port, CLIENT_REMEMBER_OPTIONS)) + { + my_bool reconnect= 1; + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (info) + { + fputs("\n",stderr); + (void) fflush(stderr); + } + return 0; + } + + if (!wait) // was or reached 0, fail + { + if (!option_silent) // print diagnostics + { + if (!host) + host= (char*) LOCAL_HOST; + my_printf_error(0,"connect to server at '%s' failed\nerror: '%s'", + error_flags, host, mysql_error(mysql)); + if (mysql_errno(mysql) == CR_CONNECTION_ERROR) + { + fprintf(stderr, + "Check that mariadbd is running and that the socket: '%s' exists!\n", + unix_port ? unix_port : mysql_unix_port); + } + else if (mysql_errno(mysql) == CR_CONN_HOST_ERROR || + mysql_errno(mysql) == CR_UNKNOWN_HOST) + { + fprintf(stderr,"Check that mariadbd is running on %s",host); + fprintf(stderr," and that the port is %d.\n", + tcp_port ? tcp_port: mysql_port); + fprintf(stderr,"You can check this by doing 'telnet %s %d'\n", + host, tcp_port ? tcp_port: mysql_port); + } + } + return 1; + } + + if (wait != (uint) ~0) + wait--; /* count down, one less retry */ + + if ((mysql_errno(mysql) != CR_CONN_HOST_ERROR) && + (mysql_errno(mysql) != CR_CONNECTION_ERROR)) + { + /* + Error is worse than "server doesn't answer (yet?)"; + fail even if we still have "wait-coins" unless --force + was also given. + */ + fprintf(stderr,"Got error: %s\n", mysql_error(mysql)); + if (!option_force) + return 1; + } + else if (!option_silent) + { + if (!info) + { + info=1; + fputs("Waiting for MariaDB server to answer",stderr); + (void) fflush(stderr); + } + else + { + putc('.',stderr); + (void) fflush(stderr); + } + } + sleep(5); + } +} + + +static int maybe_disable_binlog(MYSQL *mysql) +{ + if (opt_local && !sql_log_bin_off) + { + if (mysql_query(mysql, "set local sql_log_bin=0")) + { + my_printf_error(0, "SET LOCAL SQL_LOG_BIN=0 failed; error: '%-.200s'", + error_flags, mysql_error(mysql)); + return -1; + } + } + sql_log_bin_off= true; + return 0; +} + + +int flush(MYSQL *mysql, const char *what) +{ + char buf[FN_REFLEN]; + my_snprintf(buf, sizeof(buf), "flush %s%s", + (opt_local && !sql_log_bin_off ? "local " : ""), what); + + if (mysql_query(mysql, buf)) + { + my_printf_error(0, "flush %s failed; error: '%s'", error_flags, what, + mysql_error(mysql)); + return -1; + } + return 0; +} + + +/** + @brief Execute all commands + + @details We try to execute all commands we were given, in the order + given, but return with non-zero as soon as we encounter trouble. + By that token, individual commands can be considered a conjunction + with boolean short-cut. + + @return success? + @retval 0 Yes! ALL commands worked! + @retval 1 No, one failed and will never work (malformed): fatal error! + @retval -1 No, one failed on the server, may work next time! +*/ + +static int execute_commands(MYSQL *mysql,int argc, char **argv) +{ + int ret = 0; + const char *status; + /* + MySQL documentation relies on the fact that mysqladmin will + execute commands in the order specified, e.g. + mysqladmin -u root flush-privileges password "newpassword" + to reset a lost root password. + If this behaviour is ever changed, Docs should be notified. + */ + + struct my_rnd_struct rand_st; + char buff[FN_REFLEN + 20]; + + for (; argc > 0 ; argv++,argc--) + { + int command; + switch ((command= find_type(argv[0],&command_typelib,FIND_TYPE_BASIC))) { + case ADMIN_CREATE: + { + if (argc < 2) + { + my_printf_error(0, "Too few arguments to create", error_flags); + return 1; + } + if (maybe_disable_binlog(mysql)) + return -1; + sprintf(buff,"create database `%.*s`",FN_REFLEN,argv[1]); + if (mysql_query(mysql,buff)) + { + my_printf_error(0,"CREATE DATABASE failed; error: '%-.200s'", + error_flags, mysql_error(mysql)); + return -1; + } + argc--; argv++; + break; + } + case ADMIN_DROP: + { + if (argc < 2) + { + my_printf_error(0, "Too few arguments to drop", error_flags); + return 1; + } + if (maybe_disable_binlog(mysql)) + return -1; + if (drop_db(mysql,argv[1])) + return -1; + argc--; argv++; + break; + } + case ADMIN_SHUTDOWN: + { + char pidfile[FN_REFLEN]; + my_bool got_pidfile= 0; + time_t last_modified= 0; + struct stat pidfile_status; + + /* + Only wait for pidfile on local connections + If pidfile doesn't exist, continue without pid file checking + */ + if (mysql->unix_socket && (got_pidfile= !get_pidfile(mysql, pidfile)) && + !stat(pidfile, &pidfile_status)) + last_modified= pidfile_status.st_mtime; + + if (opt_shutdown_wait_for_slaves) + { + sprintf(buff, "SHUTDOWN WAIT FOR ALL SLAVES"); + if (mysql_query(mysql, buff)) + { + my_printf_error(0, "%s failed; error: '%-.200s'", + error_flags, buff, mysql_error(mysql)); + return -1; + } + } + else if (mysql_shutdown(mysql, SHUTDOWN_DEFAULT)) + { + my_printf_error(0, "shutdown failed; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + argc=1; /* force SHUTDOWN to be the last command */ + if (got_pidfile) + { + if (opt_verbose) + printf("Shutdown signal sent to server; Waiting for pid file to disappear\n"); + + /* Wait until pid file is gone */ + if (wait_pidfile(pidfile, last_modified, &pidfile_status)) + return -1; + } + break; + } + case ADMIN_FLUSH_PRIVILEGES: + case ADMIN_RELOAD: + if (flush(mysql, "privileges")) + return -1; + break; + case ADMIN_REFRESH: + if (mysql_refresh(mysql, + (uint) ~(REFRESH_GRANT | REFRESH_STATUS | + REFRESH_READ_LOCK | REFRESH_SLAVE | + REFRESH_MASTER))) + { + my_printf_error(0, "refresh failed; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + break; + case ADMIN_FLUSH_THREADS: + if (mysql_refresh(mysql,(uint) REFRESH_THREADS)) + { + my_printf_error(0, "refresh failed; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + break; + case ADMIN_VER: + new_line=1; + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("Server version\t\t%s\n", mysql_get_server_info(mysql)); + printf("Protocol version\t%d\n", mysql_get_proto_info(mysql)); + printf("Connection\t\t%s\n",mysql_get_host_info(mysql)); + if (mysql->unix_socket) + printf("UNIX socket\t\t%s\n", mysql->unix_socket); + else + printf("TCP port\t\t%d\n", mysql->port); + status=mysql_stat(mysql); + { + char *pos,buff[40]; + ulong sec; + pos= (char*) strchr(status,' '); + *pos++=0; + printf("%s\t\t\t",status); /* print label */ + if ((status=str2int(pos,10,0,LONG_MAX,(long*) &sec))) + { + nice_time(sec,buff); + puts(buff); /* print nice time */ + while (*status == ' ') status++; /* to next info */ + } + } + putc('\n',stdout); + if (status) + puts(status); + break; + case ADMIN_PROCESSLIST: + { + MYSQL_RES *result; + MYSQL_ROW row; + + if (mysql_query(mysql, (opt_verbose ? "show full processlist" : + "show processlist")) || + !(result = mysql_store_result(mysql))) + { + my_printf_error(0, "process list failed; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + print_header(result); + while ((row=mysql_fetch_row(result))) + print_row(result,row,0); + print_top(result); + mysql_free_result(result); + new_line=1; + break; + } + case ADMIN_STATUS: + status=mysql_stat(mysql); + if (status) + puts(status); + break; + case ADMIN_KILL: + { + uint error=0; + char *pos; + if (argc < 2) + { + my_printf_error(0, "Too few arguments to 'kill'", error_flags); + return 1; + } + pos=argv[1]; + for (;;) + { + /* We don't use mysql_kill(), since it only handles 32-bit IDs. */ + char buff[26], *out; /* "KILL " + max 20 digs + NUL */ + out= strxmov(buff, "KILL ", NullS); + ullstr(strtoull(pos, NULL, 0), out); + + if (mysql_query(mysql, buff)) + { + /* out still points to just the number */ + my_printf_error(0, "kill failed on %s; error: '%s'", error_flags, + out, mysql_error(mysql)); + error=1; + } + if (!(pos=strchr(pos,','))) + break; + pos++; + } + argc--; argv++; + if (error) + return -1; + break; + } + case ADMIN_DEBUG: + if (mysql_dump_debug_info(mysql)) + { + my_printf_error(0, "debug failed; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + break; + case ADMIN_VARIABLES: + { + MYSQL_RES *res; + MYSQL_ROW row; + + new_line=1; + if (mysql_query(mysql,"show /*!40003 GLOBAL */ variables") || + !(res=mysql_store_result(mysql))) + { + my_printf_error(0, "unable to show variables; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + print_header(res); + while ((row=mysql_fetch_row(res))) + print_row(res,row,0); + print_top(res); + mysql_free_result(res); + break; + } + case ADMIN_EXTENDED_STATUS: + { + MYSQL_RES *res; + MYSQL_ROW row; + uint rownr = 0; + void (*func) (MYSQL_RES*, MYSQL_ROW, uint); + + new_line = 1; + if (mysql_query(mysql, "show /*!50002 GLOBAL */ status") || + !(res = mysql_store_result(mysql))) + { + my_printf_error(0, "unable to show status; error: '%s'", error_flags, + mysql_error(mysql)); + return -1; + } + + DBUG_ASSERT(mysql_num_rows(res) < MAX_MYSQL_VAR+100); + + if (!opt_vertical) + print_header(res); + else + { + if (!ex_status_printed) + { + store_values(res); + truncate_names(); /* Does some printing also */ + } + else + { + print_relative_line(); + print_relative_header(); + print_relative_line(); + } + } + + /* void (*func) (MYSQL_RES*, MYSQL_ROW, uint); */ + if (opt_relative && !opt_vertical) + func = print_relative_row; + else if (opt_vertical) + func = print_relative_row_vert; + else + func = print_row; + + while ((row = mysql_fetch_row(res))) + (*func)(res, row, rownr++); + if (opt_vertical) + { + if (ex_status_printed) + { + putchar('\n'); + print_relative_line(); + } + } + else + print_top(res); + + ex_status_printed = 1; /* From now on the output will be relative */ + mysql_free_result(res); + break; + } + case ADMIN_FLUSH_LOGS: + { + if (flush(mysql, "logs")) + return -1; + break; + } + case ADMIN_FLUSH_BINARY_LOG: + { + if (flush(mysql, "binary logs")) + return -1; + break; + } + case ADMIN_FLUSH_ENGINE_LOG: + { + if (flush(mysql, "engine logs")) + return -1; + break; + } + case ADMIN_FLUSH_ERROR_LOG: + { + if (flush(mysql, "error logs")) + return -1; + break; + } + case ADMIN_FLUSH_GENERAL_LOG: + { + if (flush(mysql, "general logs")) + return -1; + break; + } + case ADMIN_FLUSH_RELAY_LOG: + { + if (flush(mysql, "relay logs")) + return -1; + break; + } + case ADMIN_FLUSH_SLOW_LOG: + { + if (flush(mysql, "slow logs")) + return -1; + break; + } + case ADMIN_FLUSH_HOSTS: + { + if (flush(mysql, "hosts")) + return -1; + break; + } + case ADMIN_FLUSH_TABLES: + { + if (flush(mysql, "tables")) + return -1; + break; + } + case ADMIN_FLUSH_STATUS: + { + if (flush(mysql, "status")) + return -1; + break; + } + case ADMIN_FLUSH_TABLE_STATISTICS: + { + if (flush(mysql, "table_statistics")) + return -1; + break; + } + case ADMIN_FLUSH_INDEX_STATISTICS: + { + if (flush(mysql, "index_statistics")) + return -1; + break; + } + case ADMIN_FLUSH_SSL: + { + if (flush(mysql, "ssl")) + return -1; + break; + } + case ADMIN_FLUSH_USER_STATISTICS: + { + if (flush(mysql, "user_statistics")) + return -1; + break; + } + case ADMIN_FLUSH_USER_RESOURCES: + { + if (flush(mysql, "user_resources")) + return -1; + break; + } + case ADMIN_FLUSH_CLIENT_STATISTICS: + { + if (flush(mysql, "client_statistics")) + return -1; + break; + } + case ADMIN_FLUSH_ALL_STATISTICS: + { + if (flush(mysql, "table_statistics,index_statistics," + "user_statistics,client_statistics")) + return -1; + break; + } + case ADMIN_FLUSH_ALL_STATUS: + { + if (flush(mysql, "status,table_statistics,index_statistics," + "user_statistics,client_statistics")) + return -1; + break; + } + case ADMIN_OLD_PASSWORD: + case ADMIN_PASSWORD: + { + char buff[128],crypted_pw[64]; + time_t start_time; + char *typed_password= NULL, *verified= NULL; + /* Do initialization the same way as we do in mysqld */ + start_time=time((time_t*) 0); + my_rnd_init(&rand_st,(ulong) start_time,(ulong) start_time/2); + + if (maybe_disable_binlog(mysql)) + return -1; + if (argc < 1) + { + my_printf_error(0, "Too few arguments to change password", error_flags); + return 1; + } + else if (argc == 1) + { + /* prompt for password */ + typed_password= my_get_tty_password("New password: "); + verified= my_get_tty_password("Confirm new password: "); + if (strcmp(typed_password, verified) != 0) + { + my_printf_error(0,"Passwords don't match",MYF(ME_BELL)); + ret = -1; + goto password_done; + } + } + else + typed_password= argv[1]; + + if (typed_password[0]) + { + bool old= (find_type(argv[0], &command_typelib, FIND_TYPE_BASIC) == + ADMIN_OLD_PASSWORD); +#ifdef _WIN32 + size_t pw_len= strlen(typed_password); + if (pw_len > 1 && typed_password[0] == '\'' && + typed_password[pw_len-1] == '\'') + printf("Warning: single quotes were not trimmed from the password by" + " your command\nline client, as you might have expected.\n"); +#endif + /* + If we don't already know to use an old-style password, see what + the server is using + */ + if (!old) + { + if (mysql_query(mysql, "SHOW VARIABLES LIKE 'old_passwords'")) + { + my_printf_error(0, "Could not determine old_passwords setting from server; error: '%s'", + error_flags, mysql_error(mysql)); + ret = -1; + goto password_done; + } + else + { + MYSQL_RES *res= mysql_store_result(mysql); + if (!res) + { + my_printf_error(0, + "Could not get old_passwords setting from " + "server; error: '%s'", + error_flags, mysql_error(mysql)); + ret = -1; + goto password_done; + } + if (!mysql_num_rows(res)) + old= 1; + else + { + MYSQL_ROW row= mysql_fetch_row(res); + old= !strncmp(row[1], "ON", 2); + } + mysql_free_result(res); + } + } + if (old) + my_make_scrambled_password_323(crypted_pw, typed_password, strlen(typed_password)); + else + my_make_scrambled_password(crypted_pw, typed_password, strlen(typed_password)); + } + else + crypted_pw[0]=0; /* No password */ + sprintf(buff,"set password='%s',sql_log_off=0",crypted_pw); + + if (mysql_query(mysql,"set sql_log_off=1")) + { + my_printf_error(0, "Can't turn off logging; error: '%s'", + error_flags, mysql_error(mysql)); + ret = -1; + } + else + if (mysql_query(mysql,buff)) + { + my_printf_error(0,"unable to change password; error: '%s'", + error_flags, mysql_error(mysql)); + ret = -1; + } +password_done: + /* free up memory from prompted password */ + if (typed_password != argv[1]) + { + my_free(typed_password); + my_free(verified); + } + argc--; argv++; + break; + } + + case ADMIN_START_SLAVE: + case ADMIN_START_ALL_SLAVES: + { + my_bool many_slaves= 0; + const char *query= "START SLAVE"; + if (command == ADMIN_START_ALL_SLAVES && mariadb_connection(mysql) && + mysql_get_server_version(mysql) >= 100000) + { + query="START ALL SLAVES"; + many_slaves= 1; + } + + if (mysql_query(mysql, query)) + { + my_printf_error(0, "Error starting slave: %s", error_flags, + mysql_error(mysql)); + return -1; + } + else if (!many_slaves || mysql_warning_count(mysql) > 0) + { + if (!option_silent) + puts("Slave('s) started"); + } + else + { + if (!option_silent) + puts("No slaves to start"); + } + break; + } + case ADMIN_STOP_SLAVE: + case ADMIN_STOP_ALL_SLAVES: + { + const char *query= "STOP SLAVE"; + my_bool many_slaves= 0; + + if (command == ADMIN_STOP_ALL_SLAVES && mariadb_connection(mysql) && + mysql_get_server_version(mysql) >= 100000) + { + query="STOP ALL SLAVES"; + many_slaves= 1; + } + + if (mysql_query(mysql, query)) + { + my_printf_error(0, "Error stopping slave: %s", error_flags, + mysql_error(mysql)); + return -1; + } + else if (!many_slaves || mysql_warning_count(mysql) > 0) + { + /* We can't detect if there was any slaves to stop with STOP SLAVE */ + if (many_slaves && !option_silent) + puts("Slave('s) stopped"); + } + else + { + if (!option_silent) + puts("All slaves was already stopped"); + } + break; + } + case ADMIN_PING: + { + my_bool reconnect= 0; + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (!mysql_ping(mysql)) + { + if (option_silent < 2) + puts("mysqld is alive"); + } + else + { + if (mysql_errno(mysql) == CR_SERVER_GONE_ERROR) + { + reconnect= 1; + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (!mysql_ping(mysql)) + puts("connection was down, but mysqld is now alive"); + } + else + { + my_printf_error(0,"mariadbd doesn't answer to ping, error: '%s'", + error_flags, mysql_error(mysql)); + return -1; + } + } + reconnect=1; /* Automatic reconnect is default */ + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + break; + } + default: + my_printf_error(0, "Unknown command: '%-.60s'", error_flags, argv[0]); + return 1; + } + } + return ret; +} + +/** + @brief Masking the password if it is passed as command line argument. + + @details It works in Linux and changes cmdline in ps and /proc/pid/cmdline, + but it won't work for history file of shell. + The command line arguments are copied to another array and the + password in the argv is masked. This function is called just after + "handle_options" because in "handle_options", the agrv pointers + are altered which makes freeing of dynamically allocated memory + difficult. The password masking is done before all other operations + in order to minimise the time frame of password visibility via cmdline. + + @param argc command line options (count) + @param argv command line options (values) + + @return temp_argv copy of argv +*/ + +static char **mask_password(int argc, char ***argv) +{ + char **temp_argv; + if (!argc) + return NULL; + + temp_argv= (char **)(my_malloc(PSI_NOT_INSTRUMENTED, sizeof(char *) * argc, MYF(MY_WME))); + argc--; + while (argc > 0) + { + temp_argv[argc]= my_strdup(PSI_NOT_INSTRUMENTED, (*argv)[argc], MYF(MY_FAE)); + if (find_type((*argv)[argc - 1],&command_typelib, FIND_TYPE_BASIC) == ADMIN_PASSWORD || + find_type((*argv)[argc - 1],&command_typelib, FIND_TYPE_BASIC) == ADMIN_OLD_PASSWORD) + { + char *start= (*argv)[argc]; + while (*start) + *start++= 'x'; + start= (*argv)[argc]; + if (*start) + start[1]= 0; /* Cut length of argument */ + } + argc--; + } + temp_argv[argc]= my_strdup(PSI_NOT_INSTRUMENTED, (*argv)[argc], MYF(MY_FAE)); + return(temp_argv); +} + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s on %s\n",my_progname,ADMIN_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + + +static void usage(void) +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + puts("Administration program for the mysqld daemon."); + printf("Usage: %s [OPTIONS] command command....\n", my_progname); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); + puts("\nWhere command is a one or more of: (Commands may be shortened)\n\ + create databasename Create a new database\n\ + debug Instruct server to write debug information to log\n\ + drop databasename Delete a database and all its tables\n\ + extended-status Gives an extended status message from the server\n\ + flush-all-statistics Flush all statistics tables\n\ + flush-all-status Flush status and statistics\n\ + flush-client-statistics Flush client statistics\n\ + flush-hosts Flush all cached hosts\n\ + flush-index-statistics Flush index statistics\n\ + flush-logs Flush all logs\n\ + flush-privileges Reload grant tables (same as reload)\n\ + flush-binary-log Flush binary log\n\ + flush-engine-log Flush engine log(s)\n\ + flush-error-log Flush error log\n\ + flush-general-log Flush general log\n\ + flush-relay-log Flush relay log\n\ + flush-slow-log Flush slow query log\n\ + flush-ssl Flush SSL certificates\n\ + flush-status Clear status variables\n\ + flush-table-statistics Clear table statistics\n\ + flush-tables Flush all tables\n\ + flush-threads Flush the thread cache\n\ + flush-user-statistics Flush user statistics\n\ + flush-user-resources Flush user resources\n\ + kill id,id,... Kill mysql threads"); +#if MYSQL_VERSION_ID >= 32200 + puts("\ + password [new-password] Change old password to new-password in current format\n\ + old-password [new-password] Change old password to new-password in old format"); +#endif + puts("\ + ping Check if mysqld is alive\n\ + processlist Show list of active threads in server\n\ + reload Reload grant tables\n\ + refresh Flush all tables and close and open logfiles\n\ + shutdown Take server down\n\ + status Gives a short status message from the server\n\ + start-slave Start slave\n\ + stop-slave Stop slave\n\ + variables Prints variables available\n\ + version Get version info from server"); +} + + +static int drop_db(MYSQL *mysql, const char *db) +{ + char name_buff[FN_REFLEN+20], buf[10]; + char *input; + + if (!option_force) + { + puts("Dropping the database is potentially a very bad thing to do."); + puts("Any data stored in the database will be destroyed.\n"); + printf("Do you really want to drop the '%s' database [y/N] ",db); + fflush(stdout); + input= fgets(buf, sizeof(buf)-1, stdin); + if (!input || ((*input != 'y') && (*input != 'Y'))) + { + puts("\nOK, aborting database drop!"); + return -1; + } + } + sprintf(name_buff,"drop database `%.*s`",FN_REFLEN,db); + if (mysql_query(mysql,name_buff)) + { + my_printf_error(0, "DROP DATABASE %s failed;\nerror: '%s'", error_flags, + db,mysql_error(mysql)); + return 1; + } + printf("Database \"%s\" dropped\n",db); + return 0; +} + + +static void nice_time(ulong sec,char *buff) +{ + ulong tmp; + + if (sec >= 3600L*24) + { + tmp=sec/(3600L*24); + sec-=3600L*24*tmp; + buff=int10_to_str(tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " days " : " day "); + } + if (sec >= 3600L) + { + tmp=sec/3600L; + sec-=3600L*tmp; + buff=int10_to_str(tmp, buff, 10); + buff=strmov(buff,tmp > 1 ? " hours " : " hour "); + } + if (sec >= 60) + { + tmp=sec/60; + sec-=60*tmp; + buff=int10_to_str(tmp, buff, 10); + buff=strmov(buff," min "); + } + strmov(int10_to_str(sec, buff, 10)," sec"); +} + + +static void print_header(MYSQL_RES *result) +{ + MYSQL_FIELD *field; + + print_top(result); + mysql_field_seek(result,0); + putchar('|'); + while ((field = mysql_fetch_field(result))) + { + printf(" %-*s|",(int) field->max_length+1,field->name); + } + putchar('\n'); + print_top(result); +} + + +static void print_top(MYSQL_RES *result) +{ + uint i,length; + MYSQL_FIELD *field; + + putchar('+'); + mysql_field_seek(result,0); + while((field = mysql_fetch_field(result))) + { + if ((length=(uint) strlen(field->name)) > field->max_length) + field->max_length=length; + else + length=field->max_length; + for (i=length+2 ; i--> 0 ; ) + putchar('-'); + putchar('+'); + } + putchar('\n'); +} + + +/* 3.rd argument, uint row, is not in use. Don't remove! */ +static void print_row(MYSQL_RES *result, MYSQL_ROW cur, + uint row __attribute__((unused))) +{ + uint i,length; + MYSQL_FIELD *field; + + putchar('|'); + mysql_field_seek(result,0); + for (i=0 ; i < mysql_num_fields(result); i++) + { + field = mysql_fetch_field(result); + length=field->max_length; + printf(" %-*s|",length+1,cur[i] ? (char*) cur[i] : ""); + } + putchar('\n'); +} + + +static void print_relative_row(MYSQL_RES *result, MYSQL_ROW cur, uint row) +{ + ulonglong tmp; + char buff[22]; + MYSQL_FIELD *field; + + mysql_field_seek(result, 0); + field = mysql_fetch_field(result); + printf("| %-*s|", (int) field->max_length + 1, cur[0]); + + field = mysql_fetch_field(result); + tmp = cur[1] ? strtoull(cur[1], NULL, 10) : (ulonglong) 0; + printf(" %-*s|\n", (int) field->max_length + 1, + llstr((tmp - last_values[row]), buff)); + last_values[row] = tmp; +} + + +static void print_relative_row_vert(MYSQL_RES *result __attribute__((unused)), + MYSQL_ROW cur, + uint row __attribute__((unused))) +{ + uint length; + ulonglong tmp; + char buff[22]; + + if (!row) + putchar('|'); + + tmp = cur[1] ? strtoull(cur[1], NULL, 10) : (ulonglong) 0; + printf(" %-*s|", ex_val_max_len[row] + 1, + llstr((tmp - last_values[row]), buff)); + + /* Find the minimum row length needed to output the relative value */ + length=(uint) strlen(buff); + if (length > ex_val_max_len[row] && ex_status_printed) + ex_val_max_len[row] = length; + last_values[row] = tmp; +} + + +static void store_values(MYSQL_RES *result) +{ + uint i; + MYSQL_ROW row; + MYSQL_FIELD *field; + + field = mysql_fetch_field(result); + max_var_length = field->max_length; + field = mysql_fetch_field(result); + max_val_length = field->max_length; + + for (i = 0; (row = mysql_fetch_row(result)); i++) + { + strmov(ex_var_names[i], row[0]); + last_values[i]=strtoull(row[1],NULL,10); + ex_val_max_len[i]=2; /* Default print width for values */ + } + ex_var_count = i; + return; +} + + +static void print_relative_header() +{ + uint i; + + putchar('|'); + for (i = 0; i < ex_var_count; i++) + printf(" %-*s|", ex_val_max_len[i] + 1, truncated_var_names[i]); + putchar('\n'); +} + + +static void print_relative_line() +{ + uint i; + + putchar('+'); + for (i = 0; i < ex_var_count; i++) + { + uint j; + for (j = 0; j < ex_val_max_len[i] + 2; j++) + putchar('-'); + putchar('+'); + } + putchar('\n'); +} + + +static void truncate_names() +{ + uint i; + char *ptr,top_line[MAX_TRUNC_LENGTH+4+NAME_LEN+22+1],buff[22]; + + ptr=top_line; + *ptr++='+'; + ptr=strfill(ptr,max_var_length+2,'-'); + *ptr++='+'; + ptr=strfill(ptr,MAX_TRUNC_LENGTH+2,'-'); + *ptr++='+'; + ptr=strfill(ptr,max_val_length+2,'-'); + *ptr++='+'; + *ptr=0; + puts(top_line); + + for (i = 0 ; i < ex_var_count; i++) + { + uint sfx=1,j; + printf("| %-*s|", max_var_length + 1, ex_var_names[i]); + ptr = ex_var_names[i]; + /* Make sure no two same truncated names will become */ + for (j = 0; j < i; j++) + if (*truncated_var_names[j] == *ptr) + sfx++; + + truncated_var_names[i][0]= *ptr; /* Copy first var char */ + int10_to_str(sfx, truncated_var_names[i]+1,10); + printf(" %-*s|", MAX_TRUNC_LENGTH + 1, truncated_var_names[i]); + printf(" %-*s|\n", max_val_length + 1, llstr(last_values[i],buff)); + } + puts(top_line); + return; +} + + +static my_bool get_pidfile(MYSQL *mysql, char *pidfile) +{ + MYSQL_RES* result; + + if (mysql_query(mysql, "SHOW VARIABLES LIKE 'pid_file'")) + { + my_printf_error(mysql_errno(mysql), + "The query to get the server's pid file failed," + " error: '%s'. Continuing.", error_flags, + mysql_error(mysql)); + } + result = mysql_store_result(mysql); + if (result) + { + MYSQL_ROW row=mysql_fetch_row(result); + if (row) + strmov(pidfile, row[1]); + mysql_free_result(result); + return row == 0; /* Error if row = 0 */ + } + return 1; /* Error */ +} + +/* + Return 1 if pid file didn't disappear or change +*/ + +static my_bool wait_pidfile(char *pidfile, time_t last_modified, + struct stat *pidfile_status) +{ + char buff[FN_REFLEN]; + my_bool error= 1; + uint count= 0; + DBUG_ENTER("wait_pidfile"); + + system_filename(buff, pidfile); + do + { + int fd; + if ((fd= my_open(buff, O_RDONLY, MYF(0))) < 0) + { + error= 0; + break; + } + (void) my_close(fd,MYF(0)); + if (last_modified && !stat(pidfile, pidfile_status)) + { + if (last_modified != pidfile_status->st_mtime) + { + /* File changed; Let's assume that mysqld did restart */ + if (opt_verbose) + printf("pid file '%s' changed while waiting for it to disappear!\nmysqld did probably restart\n", + buff); + error= 0; + break; + } + } + if (count++ == opt_shutdown_timeout) + break; + sleep(1); + } while (!interrupted); + + if (error) + { + DBUG_PRINT("warning",("Pid file didn't disappear")); + fprintf(stderr, + "Warning; Aborted waiting on pid file: '%s' after %d seconds\n", + buff, count-1); + } + DBUG_RETURN(error); +} diff --git a/client/mysqlbinlog.cc b/client/mysqlbinlog.cc new file mode 100644 index 00000000..3d22f091 --- /dev/null +++ b/client/mysqlbinlog.cc @@ -0,0 +1,3802 @@ +/* + Copyright (c) 2000, 2014, Oracle and/or its affiliates. + Copyright (c) 2009, 2020, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* + + TODO: print the catalog (some USE catalog.db ????). + + Standalone program to read a MySQL binary log (or relay log). + + Should be able to read any file of these categories, even with + --start-position. + An important fact: the Format_desc event of the log is at most the 3rd event + of the log; if it is the 3rd then there is this combination: + Format_desc_of_slave, Rotate_of_master, Format_desc_of_master. +*/ + +#define MYSQL_CLIENT +#undef MYSQL_SERVER +#define TABLE TABLE_CLIENT +/* This hack is here to avoid adding COMPRESSED data types to libmariadb. */ +#define MYSQL_TYPE_TIME2 MYSQL_TYPE_TIME2,MYSQL_TYPE_BLOB_COMPRESSED=140,MYSQL_TYPE_VARCHAR_COMPRESSED=141 +#include "client_priv.h" +#undef MYSQL_TYPE_TIME2 +#include +#include +/* That one is necessary for defines of OPTION_NO_FOREIGN_KEY_CHECKS etc */ +#include "sql_priv.h" +#include "sql_basic_types.h" +#include +#include "log_event.h" +#include "compat56.h" +#include "sql_common.h" +#include "my_dir.h" +#include // ORACLE_WELCOME_COPYRIGHT_NOTICE +#include "rpl_gtid.h" +#include "sql_string.h" // needed for Rpl_filter +#include "sql_list.h" // needed for Rpl_filter +#include "rpl_filter.h" + +#include "mysqld.h" + +#include + +#define my_net_write ma_net_write +#define net_flush ma_net_flush +#define cli_safe_read mysql_net_read_packet +#define my_net_read ma_net_read +extern "C" unsigned char *mysql_net_store_length(unsigned char *packet, size_t length); +#define net_store_length mysql_net_store_length + +#define key_memory_TABLE_RULE_ENT 0 +#define key_memory_rpl_filter 0 + +Rpl_filter *binlog_filter= 0; + +#define BIN_LOG_HEADER_SIZE 4 +#define PROBE_HEADER_LEN (EVENT_LEN_OFFSET+4) + +/* Needed for Rpl_filter */ +CHARSET_INFO* system_charset_info= &my_charset_utf8mb3_general_ci; + +/* Needed for Flashback */ +DYNAMIC_ARRAY binlog_events; // Storing the events output string +DYNAMIC_ARRAY events_in_stmt; // Storing the events that in one statement +String stop_event_string; // Storing the STOP_EVENT output string + +extern "C" { +char server_version[SERVER_VERSION_LENGTH]; +} + +static char *server_id_str; + +// needed by net_serv.c +ulong bytes_sent = 0L, bytes_received = 0L; +ulong mysqld_net_retry_count = 10L; +ulong open_files_limit; +ulong opt_binlog_rows_event_max_size; +ulonglong test_flags = 0; +ulong opt_binlog_rows_event_max_encoded_size= MAX_MAX_ALLOWED_PACKET; +static uint opt_protocol= 0; +static FILE *result_file; +static char *result_file_name= 0; +static const char *output_prefix= ""; +static char **defaults_argv= 0; +static MEM_ROOT glob_root; + +#ifndef DBUG_OFF +static const char *default_dbug_option = "d:t:o,/tmp/mariadb-binlog.trace"; +const char *current_dbug_option= default_dbug_option; +#endif +static const char *load_groups[]= +{ "mysqlbinlog", "mariadb-binlog", "client", "client-server", "client-mariadb", + 0 }; + +static void error(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2); +static void warning(const char *format, ...) ATTRIBUTE_FORMAT(printf, 1, 2); + +static bool one_database=0, one_table=0, to_last_remote_log= 0, disable_log_bin= 0; +static bool opt_hexdump= 0, opt_version= 0; +const char *base64_output_mode_names[]= +{"NEVER", "AUTO", "UNSPEC", "DECODE-ROWS", NullS}; +TYPELIB base64_output_mode_typelib= + { array_elements(base64_output_mode_names) - 1, "", + base64_output_mode_names, NULL }; +static enum_base64_output_mode opt_base64_output_mode= BASE64_OUTPUT_UNSPEC; +static char *opt_base64_output_mode_str= NullS; +static char* database= 0; +static char* table= 0; +static my_bool force_opt= 0, short_form= 0, remote_opt= 0; +static my_bool print_row_count= 0, print_row_event_positions= 0; +static my_bool print_row_count_used= 0, print_row_event_positions_used= 0; +static my_bool debug_info_flag, debug_check_flag; +static my_bool force_if_open_opt= 1; +static my_bool opt_raw_mode= 0, opt_stop_never= 0; +my_bool opt_gtid_strict_mode= true; +static ulong opt_stop_never_slave_server_id= 0; +static my_bool opt_verify_binlog_checksum= 1; +static ulonglong offset = 0; +static char* host = 0; +static int port= 0; +static uint my_end_arg; +static const char* sock= 0; +static char *opt_plugindir= 0, *opt_default_auth= 0; + +static char* user = 0; +static char* pass = 0; +static char *charset= 0; + +static uint verbose= 0; + +static char *ignore_domain_ids_str, *do_domain_ids_str; +static char *ignore_server_ids_str, *do_server_ids_str; +static char *start_pos_str, *stop_pos_str; +static ulonglong start_position= BIN_LOG_HEADER_SIZE, + stop_position= (longlong)(~(my_off_t)0) ; +#define start_position_mot ((my_off_t)start_position) +#define stop_position_mot ((my_off_t)stop_position) + +static Binlog_gtid_state_validator *gtid_state_validator= NULL; +static Gtid_event_filter *gtid_event_filter= NULL; +static Domain_gtid_event_filter *position_gtid_filter= NULL; +static Domain_gtid_event_filter *domain_id_gtid_filter= NULL; +static Server_gtid_event_filter *server_id_gtid_filter= NULL; + +static char *start_datetime_str, *stop_datetime_str; +static my_time_t start_datetime= 0, stop_datetime= MY_TIME_T_MAX; +static ulonglong rec_count= 0; +static MYSQL* mysql = NULL; +static const char* dirname_for_local_load= 0; +static bool opt_skip_annotate_row_events= 0; + +static my_bool opt_flashback; +static bool opt_print_table_metadata; +#ifdef WHEN_FLASHBACK_REVIEW_READY +static my_bool opt_flashback_review; +static char *flashback_review_dbname, *flashback_review_tablename; +#endif + +/** + Pointer to the Format_description_log_event of the currently active binlog. + + This will be changed each time a new Format_description_log_event is + found in the binlog. It is finally destroyed at program termination. +*/ +static Format_description_log_event* glob_description_event= NULL; + +/** + Exit status for functions in this file. +*/ +enum Exit_status { + /** No error occurred and execution should continue. */ + OK_CONTINUE= 0, + /** An error occurred and execution should stop. */ + ERROR_STOP, + /** No error occurred but execution should stop. */ + OK_STOP, + /** No error occurred - end of file reached. */ + OK_EOF, +}; + +/** + Pointer to the last read Annotate_rows_log_event. Having read an + Annotate_rows event, we should not print it immediately because all + subsequent rbr events can be filtered away, and have to keep it for a while. + Also because of that when reading a remote Annotate event we have to keep + its binary log representation in a separately allocated buffer. +*/ +static Annotate_rows_log_event *annotate_event= NULL; + +static void free_annotate_event() +{ + if (annotate_event) + { + delete annotate_event; + annotate_event= 0; + } +} + +Log_event* read_remote_annotate_event(uchar* net_buf, ulong event_len, + const char **error_msg) +{ + uchar *event_buf; + Log_event* event; + + if (!(event_buf= (uchar*) my_malloc(PSI_NOT_INSTRUMENTED, event_len + 1, MYF(MY_WME)))) + { + error("Out of memory"); + return 0; + } + + memcpy(event_buf, net_buf, event_len); + event_buf[event_len]= 0; + + if (!(event= Log_event::read_log_event(event_buf, event_len, + error_msg, glob_description_event, + opt_verify_binlog_checksum))) + { + my_free(event_buf); + return 0; + } + /* + Ensure the event->temp_buf is pointing to the allocated buffer. + (TRUE = free temp_buf on the event deletion) + */ + event->register_temp_buf(event_buf, TRUE); + + return event; +} + +void keep_annotate_event(Annotate_rows_log_event* event) +{ + free_annotate_event(); + annotate_event= event; +} + +bool print_annotate_event(PRINT_EVENT_INFO *print_event_info) +{ + bool error= 0; + if (annotate_event) + { + annotate_event->print(result_file, print_event_info); + free_annotate_event(); + } + return error; +} + +static Exit_status dump_local_log_entries(PRINT_EVENT_INFO *, const char*); +static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *, const char*); +static Exit_status dump_log_entries(const char* logname); +static Exit_status safe_connect(); + + +class Load_log_processor +{ + char target_dir_name[FN_REFLEN]; + size_t target_dir_name_len; + + /* + When we see first event corresponding to some LOAD DATA statement in + binlog, we create temporary file to store data to be loaded. + We add name of this file to file_names array using its file_id as index. + If we have Create_file event (i.e. we have binary log in pre-5.0.3 + format) we also store save event object to be able which is needed to + emit LOAD DATA statement when we will meet Exec_load_data event. + If we have Begin_load_query event we simply store 0 in + File_name_record::event field. + */ + struct File_name_record + { + char *fname; + Create_file_log_event *event; + }; + /* + @todo Should be a map (e.g., a hash map), not an array. With the + present implementation, the number of elements in this array is + about the number of files loaded since the server started, which + may be big after a few years. We should be able to use existing + library data structures for this. /Sven + */ + DYNAMIC_ARRAY file_names; + + /** + Looks for a non-existing filename by adding a numerical suffix to + the given base name, creates the generated file, and returns the + filename by modifying the filename argument. + + @param[in,out] filename Base filename + + @param[in,out] file_name_end Pointer to last character of + filename. The numerical suffix will be written to this position. + Note that there must be a least five bytes of allocated memory + after file_name_end. + + @retval -1 Error (can't find new filename). + @retval >=0 Found file. + */ + File create_unique_file(char *filename, char *file_name_end) + { + File res; + /* If we have to try more than 1000 times, something is seriously wrong */ + for (uint version= 0; version<1000; version++) + { + sprintf(file_name_end,"-%x",version); + if ((res= my_create(filename,0, + O_CREAT|O_EXCL|O_BINARY|O_WRONLY,MYF(0)))!=-1) + return res; + } + return -1; + } + +public: + Load_log_processor() = default; + ~Load_log_processor() = default; + + int init() + { + return my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &file_names, sizeof(File_name_record), + 100, 100, MYF(0)); + } + + void init_by_dir_name(const char *dir) + { + target_dir_name_len= (convert_dirname(target_dir_name, dir, NullS) - + target_dir_name); + } + void init_by_cur_dir() + { + if (my_getwd(target_dir_name,sizeof(target_dir_name),MYF(MY_WME))) + exit(1); + target_dir_name_len= strlen(target_dir_name); + } + void destroy() + { + File_name_record *ptr= (File_name_record *)file_names.buffer; + File_name_record *end= ptr + file_names.elements; + for (; ptr < end; ptr++) + { + if (ptr->fname) + { + my_free(ptr->fname); + delete ptr->event; + bzero((char *)ptr, sizeof(File_name_record)); + } + } + + delete_dynamic(&file_names); + } + + /** + Obtain Create_file event for LOAD DATA statement by its file_id + and remove it from this Load_log_processor's list of events. + + Checks whether we have already seen a Create_file_log_event with + the given file_id. If yes, returns a pointer to the event and + removes the event from array describing active temporary files. + From this moment, the caller is responsible for freeing the memory + occupied by the event. + + @param[in] file_id File id identifying LOAD DATA statement. + + @return Pointer to Create_file_log_event, or NULL if we have not + seen any Create_file_log_event with this file_id. + */ + Create_file_log_event *grab_event(uint file_id) + { + File_name_record *ptr; + Create_file_log_event *res; + + if (file_id >= file_names.elements) + return 0; + ptr= dynamic_element(&file_names, file_id, File_name_record*); + if ((res= ptr->event)) + bzero((char *)ptr, sizeof(File_name_record)); + return res; + } + + /** + Obtain file name of temporary file for LOAD DATA statement by its + file_id and remove it from this Load_log_processor's list of events. + + @param[in] file_id Identifier for the LOAD DATA statement. + + Checks whether we have already seen Begin_load_query event for + this file_id. If yes, returns the file name of the corresponding + temporary file and removes the filename from the array of active + temporary files. From this moment, the caller is responsible for + freeing the memory occupied by this name. + + @return String with the name of the temporary file, or NULL if we + have not seen any Begin_load_query_event with this file_id. + */ + char *grab_fname(uint file_id) + { + File_name_record *ptr; + char *res= 0; + + if (file_id >= file_names.elements) + return 0; + ptr= dynamic_element(&file_names, file_id, File_name_record*); + if (!ptr->event) + { + res= ptr->fname; + bzero((char *)ptr, sizeof(File_name_record)); + } + return res; + } + Exit_status process(Create_file_log_event *ce); + Exit_status process(Begin_load_query_log_event *ce); + Exit_status process(Append_block_log_event *ae); + File prepare_new_file_for_old_format(Load_log_event *le, char *filename); + Exit_status load_old_format_file(NET* net, const char *server_fname, + uint server_fname_len, File file); + Exit_status process_first_event(const char *bname, size_t blen, + const uchar *block, + size_t block_len, uint file_id, + Create_file_log_event *ce); +}; + + +/** + Creates and opens a new temporary file in the directory specified by previous call to init_by_dir_name() or init_by_cur_dir(). + + @param[in] le The basename of the created file will start with the + basename of the file pointed to by this Load_log_event. + + @param[out] filename Buffer to save the filename in. + + @return File handle >= 0 on success, -1 on error. +*/ +File Load_log_processor::prepare_new_file_for_old_format(Load_log_event *le, + char *filename) +{ + size_t len; + char *tail; + File file; + + fn_format(filename, le->fname, target_dir_name, "", MY_REPLACE_DIR); + len= strlen(filename); + tail= filename + len; + + if ((file= create_unique_file(filename,tail)) < 0) + { + error("Could not construct local filename %s.",filename); + return -1; + } + + le->set_fname_outside_temp_buf(filename,len+strlen(tail)); + + return file; +} + + +/** + Reads a file from a server and saves it locally. + + @param[in,out] net The server to read from. + + @param[in] server_fname The name of the file that the server should + read. + + @param[in] server_fname_len The length of server_fname. + + @param[in,out] file The file to write to. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +Exit_status Load_log_processor::load_old_format_file(NET* net, + const char*server_fname, + uint server_fname_len, + File file) +{ + uchar buf[FN_REFLEN+1]; + buf[0] = 0; + memcpy(buf + 1, server_fname, server_fname_len + 1); + if (my_net_write(net, buf, server_fname_len +2) || net_flush(net)) + { + error("Failed requesting the remote dump of %s.", server_fname); + return ERROR_STOP; + } + + for (;;) + { + ulong packet_len = my_net_read(net); + if (packet_len == 0) + { + if (my_net_write(net, (uchar*) "", 0) || net_flush(net)) + { + error("Failed sending the ack packet."); + return ERROR_STOP; + } + /* + we just need to send something, as the server will read but + not examine the packet - this is because mysql_load() sends + an OK when it is done + */ + break; + } + else if (packet_len == packet_error) + { + error("Failed reading a packet during the dump of %s.", server_fname); + return ERROR_STOP; + } + + if (packet_len > UINT_MAX) + { + error("Illegal length of packet read from net."); + return ERROR_STOP; + } + if (my_write(file, net->read_pos, (uint) packet_len, MYF(MY_WME|MY_NABP))) + return ERROR_STOP; + } + + return OK_CONTINUE; +} + + +/** + Process the first event in the sequence of events representing a + LOAD DATA statement. + + Creates a temporary file to be used in LOAD DATA and writes first + block of data to it. Registers its file name (and optional + Create_file event) in the array of active temporary files. + + @param bname Base name for temporary file to be created. + @param blen Base name length. + @param block First block of data to be loaded. + @param block_len First block length. + @param file_id Identifies the LOAD DATA statement. + @param ce Pointer to Create_file event object if we are processing + this type of event. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +Exit_status Load_log_processor::process_first_event(const char *bname, + size_t blen, + const uchar *block, + size_t block_len, + uint file_id, + Create_file_log_event *ce) +{ + size_t full_len= target_dir_name_len + blen + 9 + 9 + 1; + Exit_status retval= OK_CONTINUE; + char *fname, *ptr; + File file; + File_name_record rec; + DBUG_ENTER("Load_log_processor::process_first_event"); + + if (!(fname= (char*) my_malloc(PSI_NOT_INSTRUMENTED, full_len,MYF(MY_WME)))) + { + error("Out of memory."); + delete ce; + DBUG_RETURN(ERROR_STOP); + } + + memcpy(fname, target_dir_name, target_dir_name_len); + ptr= fname + target_dir_name_len; + memcpy(ptr,bname,blen); + ptr+= blen; + ptr+= sprintf(ptr, "-%x", file_id); + + if ((file= create_unique_file(fname,ptr)) < 0) + { + error("Could not construct local filename %s%s.", + target_dir_name,bname); + my_free(fname); + delete ce; + DBUG_RETURN(ERROR_STOP); + } + + rec.fname= fname; + rec.event= ce; + + /* + fname is freed in process_event() + after Execute_load_query_log_event or Execute_load_log_event + will have been processed, otherwise in Load_log_processor::destroy() + */ + if (set_dynamic(&file_names, (uchar*)&rec, file_id)) + { + error("Out of memory."); + my_free(fname); + delete ce; + DBUG_RETURN(ERROR_STOP); + } + + if (ce) + ce->set_fname_outside_temp_buf(fname, strlen(fname)); + + if (my_write(file, (uchar*)block, block_len, MYF(MY_WME|MY_NABP))) + { + error("Failed writing to file."); + retval= ERROR_STOP; + } + if (my_close(file, MYF(MY_WME))) + { + error("Failed closing file."); + retval= ERROR_STOP; + } + DBUG_RETURN(retval); +} + + +/** + Process the given Create_file_log_event. + + @see Load_log_processor::process_first_event(const char*,uint,const char*,uint,uint,Create_file_log_event*) + + @param ce Create_file_log_event to process. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +Exit_status Load_log_processor::process(Create_file_log_event *ce) +{ + const char *bname= ce->fname + dirname_length(ce->fname); + size_t blen= ce->fname_len - (bname-ce->fname); + + return process_first_event(bname, blen, ce->block, ce->block_len, + ce->file_id, ce); +} + + +/** + Process the given Begin_load_query_log_event. + + @see Load_log_processor::process_first_event(const char*,uint,const char*,uint,uint,Create_file_log_event*) + + @param ce Begin_load_query_log_event to process. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +Exit_status Load_log_processor::process(Begin_load_query_log_event *blqe) +{ + return process_first_event("SQL_LOAD_MB", 11, blqe->block, blqe->block_len, + blqe->file_id, 0); +} + + +/** + Process the given Append_block_log_event. + + Appends the chunk of the file contents specified by the event to the + file created by a previous Begin_load_query_log_event or + Create_file_log_event. + + If the file_id for the event does not correspond to any file + previously registered through a Begin_load_query_log_event or + Create_file_log_event, this member function will print a warning and + return OK_CONTINUE. It is safe to return OK_CONTINUE, because no + query will be written for this event. We should not print an error + and fail, since the missing file_id could be because a (valid) + --start-position has been specified after the Begin/Create event but + before this Append event. + + @param ae Append_block_log_event to process. + + @retval ERROR_STOP An error occurred - the program should terminate. + + @retval OK_CONTINUE No error, the program should continue. +*/ +Exit_status Load_log_processor::process(Append_block_log_event *ae) +{ + DBUG_ENTER("Load_log_processor::process"); + const char* fname= ((ae->file_id < file_names.elements) ? + dynamic_element(&file_names, ae->file_id, + File_name_record*)->fname : 0); + + if (fname) + { + File file; + Exit_status retval= OK_CONTINUE; + if (((file= my_open(fname, + O_APPEND|O_BINARY|O_WRONLY,MYF(MY_WME))) < 0)) + { + error("Failed opening file %s", fname); + DBUG_RETURN(ERROR_STOP); + } + if (my_write(file,(uchar*)ae->block,ae->block_len,MYF(MY_WME|MY_NABP))) + { + error("Failed writing to file %s", fname); + retval= ERROR_STOP; + } + if (my_close(file,MYF(MY_WME))) + { + error("Failed closing file %s", fname); + retval= ERROR_STOP; + } + DBUG_RETURN(retval); + } + + /* + There is no Create_file event (a bad binlog or a big + --start-position). Assuming it's a big --start-position, we just do + nothing and print a warning. + */ + warning("Ignoring Append_block as there is no " + "Create_file event for file_id: %u", ae->file_id); + DBUG_RETURN(OK_CONTINUE); +} + + +static Load_log_processor load_processor; + + +/** + Replace windows-style backslashes by forward slashes so it can be + consumed by the mysql client, which requires Unix path. + + @todo This is only useful under windows, so may be ifdef'ed out on + other systems. /Sven + + @todo If a Create_file_log_event contains a filename with a + backslash (valid under unix), then we have problems under windows. + /Sven + + @param[in,out] fname Filename to modify. The filename is modified + in-place. +*/ +static void convert_path_to_forward_slashes(char *fname) +{ + while (*fname) + { + if (*fname == '\\') + *fname= '/'; + fname++; + } +} + + +/** + Indicates whether the given database should be filtered out, + according to the --database=X option. + + @param log_dbname Name of database. + + @return nonzero if the database with the given name should be + filtered out, 0 otherwise. +*/ +static bool shall_skip_database(const char *log_dbname) +{ + return one_database && + (log_dbname != NULL) && + strcmp(log_dbname, database); +} + + +/** + Print "use " statement when current db is to be changed. + + We have to control emitting USE statements according to rewrite-db options. + We have to do it here (see process_event() below) and to suppress + producing USE statements by corresponding log event print-functions. +*/ + +static void +print_use_stmt(PRINT_EVENT_INFO* pinfo, const Query_log_event *ev) +{ + const char* db= ev->db; + const size_t db_len= ev->db_len; + + // pinfo->db is the current db. + // If current db is the same as required db, do nothing. + if ((ev->flags & LOG_EVENT_SUPPRESS_USE_F) || !db || + !memcmp(pinfo->db, db, db_len + 1)) + return; + + // Current db and required db are different. + // Check for rewrite rule for required db. (Note that in a rewrite rule + // neither db_from nor db_to part can be empty). + size_t len_to= 0; + const char *db_to= binlog_filter->get_rewrite_db(db, &len_to); + + // If there is no rewrite rule for db (in this case len_to is left = 0), + // printing of the corresponding USE statement is left for log event + // print-function. + if (!len_to) + return; + + // In case of rewrite rule print USE statement for db_to + my_fprintf(result_file, "use %`s%s\n", db_to, pinfo->delimiter); + + // Copy the *original* db to pinfo to suppress emitting + // of USE stmts by log_event print-functions. + memcpy(pinfo->db, db, db_len + 1); +} + + +/** + Print "SET skip_replication=..." statement when needed. + + Not all servers support this (only MariaDB from some version on). So we + mark the SET to only execute from the version of MariaDB that supports it, + and also only output it if we actually see events with the flag set, to not + get spurious errors on MySQL@Oracle servers of higher version that do not + support the flag. + + So we start out assuming @@skip_replication is 0, and only output a SET + statement when it changes. +*/ +static void +print_skip_replication_statement(PRINT_EVENT_INFO *pinfo, const Log_event *ev) +{ + bool cur_val; + + cur_val= (ev->flags & LOG_EVENT_SKIP_REPLICATION_F) != 0; + if (cur_val == pinfo->skip_replication) + return; /* Not changed. */ + fprintf(result_file, "/*!50521 SET skip_replication=%d*/%s\n", + cur_val, pinfo->delimiter); + pinfo->skip_replication= cur_val; +} + +/** + Indicates whether the given table should be filtered out, + according to the --table=X option. + + @param log_tblname Name of table. + + @return nonzero if the table with the given name should be + filtered out, 0 otherwise. +*/ +static bool shall_skip_table(const char *log_tblname) +{ + return one_table && + (log_tblname != NULL) && + strcmp(log_tblname, table); +} + +static bool print_base64(PRINT_EVENT_INFO *print_event_info, Log_event *ev) +{ + /* + These events must be printed in base64 format, if printed. + base64 format requires a FD event to be safe, so if no FD + event has been printed, we give an error. Except if user + passed --short-form, because --short-form disables printing + row events. + */ + + if (!print_event_info->printed_fd_event && !short_form && + opt_base64_output_mode != BASE64_OUTPUT_DECODE_ROWS && + opt_base64_output_mode != BASE64_OUTPUT_NEVER) + { + const char* type_str= ev->get_type_str(); + error("malformed binlog: it does not contain any " + "Format_description_log_event. Found a %s event, which " + "is not safe to process without a " + "Format_description_log_event.", + type_str); + return 1; + } + + return ev->print(result_file, print_event_info); +} + + +static bool print_row_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, + ulonglong table_id, bool is_stmt_end) +{ + Table_map_log_event *ignored_map= + print_event_info->m_table_map_ignored.get_table(table_id); + bool skip_event= (ignored_map != NULL); + char ll_buff[21]; + bool result= 0; + + if (opt_flashback) + { + Rows_log_event *e= (Rows_log_event*) ev; + // The last Row_log_event will be the first event in Flashback + if (is_stmt_end) + e->clear_flags(Rows_log_event::STMT_END_F); + // The first Row_log_event will be the last event in Flashback + if (events_in_stmt.elements == 0) + e->set_flags(Rows_log_event::STMT_END_F); + // Update the temp_buf + e->update_flags(); + + if (insert_dynamic(&events_in_stmt, (uchar *) &ev)) + { + error("Out of memory: can't allocate memory to store the flashback events."); + exit(1); + } + } + + /* + end of statement check: + i) destroy/free ignored maps + ii) if skip event + a) since we are skipping the last event, + append END-MARKER(') to body cache (if required) + + b) flush cache now + */ + if (is_stmt_end) + { + /* + Now is safe to clear ignored map (clear_tables will also + delete original table map events stored in the map). + */ + if (print_event_info->m_table_map_ignored.count() > 0) + print_event_info->m_table_map_ignored.clear_tables(); + + /* + If there is a kept Annotate event and all corresponding + rbr-events were filtered away, the Annotate event was not + freed and it is just the time to do it. + */ + free_annotate_event(); + + /* + One needs to take into account an event that gets + filtered but was last event in the statement. If this is + the case, previous rows events that were written into + IO_CACHEs still need to be copied from cache to + result_file (as it would happen in ev->print(...) if + event was not skipped). + */ + if (skip_event) + { + // append END-MARKER(') with delimiter + IO_CACHE *const body_cache= &print_event_info->body_cache; + if (my_b_tell(body_cache)) + my_b_printf(body_cache, "'%s\n", print_event_info->delimiter); + + // flush cache + if ((copy_event_cache_to_file_and_reinit(&print_event_info->head_cache, + result_file) || + copy_event_cache_to_file_and_reinit(&print_event_info->body_cache, + result_file) || + copy_event_cache_to_file_and_reinit(&print_event_info->tail_cache, + result_file))) + return 1; + } + } + + /* skip the event check */ + if (skip_event) + return 0; + + if (!opt_flashback) + result= print_base64(print_event_info, ev); + else + { + if (is_stmt_end) + { + Log_event *e= NULL; + + // Print the row_event from the last one to the first one + for (size_t i= events_in_stmt.elements; i > 0; --i) + { + e= *(dynamic_element(&events_in_stmt, i - 1, Log_event**)); + result= result || print_base64(print_event_info, e); + } + // Copy all output into the Log_event + ev->output_buf.copy(e->output_buf); + // Delete Log_event + for (size_t i= 0; i < events_in_stmt.elements-1; ++i) + { + e= *(dynamic_element(&events_in_stmt, i, Log_event**)); + delete e; + } + reset_dynamic(&events_in_stmt); + } + } + + if (is_stmt_end && !result) + { + if (print_event_info->print_row_count) + fprintf(result_file, "# Number of rows: %s\n", + llstr(print_event_info->row_events, ll_buff)); + print_event_info->row_events= 0; + } + return result; +} + +/* + Check if the server id should be excluded from the output. +*/ +static inline my_bool is_server_id_excluded(uint32 server_id) +{ + static rpl_gtid server_tester_gtid; + server_tester_gtid.server_id= server_id; + return server_id_gtid_filter == NULL + ? FALSE // No server id filter exists + : server_id_gtid_filter->exclude(&server_tester_gtid); +} + +/** + Print the given event, and either delete it or delegate the deletion + to someone else. + + The deletion may be delegated in two cases: (1) the event is a + Format_description_log_event, and is saved in + glob_description_event; (2) the event is a Create_file_log_event, + and is saved in load_processor. + + @param[in,out] print_event_info Parameters and context state + determining how to print. + @param[in] ev Log_event to process. + @param[in] pos Offset from beginning of binlog file. + @param[in] logname Name of input binlog. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. + @retval OK_STOP No error, but the end of the specified range of + events to process has been reached and the program should terminate. +*/ +Exit_status process_event(PRINT_EVENT_INFO *print_event_info, Log_event *ev, + my_off_t pos, const char *logname) +{ + char ll_buff[21]; + Log_event_type ev_type= ev->get_type_code(); + my_bool destroy_evt= TRUE; + my_bool gtid_err= FALSE; + DBUG_ENTER("process_event"); + Exit_status retval= OK_CONTINUE; + IO_CACHE *const head= &print_event_info->head_cache; + + /* + We use Gtid_list_log_event information to determine if there is missing + data between where a user expects events to start/stop (i.e. the GTIDs + provided by --start-position and --stop-position), and the true start of + the specified binary logs. The first GLLE provides the initial state of the + binary logs. + + If --start-position is provided as a file offset, we want to skip initial + GTID state verification + */ + static my_bool was_first_glle_processed= start_position > BIN_LOG_HEADER_SIZE; + + /* Bypass flashback settings to event */ + ev->is_flashback= opt_flashback; +#ifdef WHEN_FLASHBACK_REVIEW_READY + ev->need_flashback_review= opt_flashback_review; +#endif + + /* + Run time estimation of the output window configuration. + + Do not validate GLLE information is start position is provided as a file + offset. + */ + if (ev_type == GTID_LIST_EVENT && ev->when) + { + Gtid_list_log_event *glev= (Gtid_list_log_event *)ev; + + /* + If this is the first Gtid_list_log_event, initialize the state of the + GTID stream auditor to be consistent with the binary logs provided + */ + if (gtid_state_validator && !was_first_glle_processed && glev->count) + { + if (gtid_state_validator->initialize_gtid_state(stderr, glev->list, + glev->count)) + goto err; + + if (position_gtid_filter && + !position_gtid_filter->get_num_start_gtids()) + { + /* + We need to validate the GTID list from --stop-position because we + couldn't prove it intrinsically (i.e. using stop > start) + */ + rpl_gtid *stop_gtids= position_gtid_filter->get_stop_gtids(); + size_t n_stop_gtids= position_gtid_filter->get_num_stop_gtids(); + if (gtid_state_validator->verify_stop_state(stderr, stop_gtids, + n_stop_gtids)) + { + my_free(stop_gtids); + goto err; + } + my_free(stop_gtids); + } + } + + /* + Verify that we are able to process events from this binlog. For example, + if our current GTID state is behind the state of the GLLE in the new log, + a user may have accidentally left out a log file to process. + */ + if (gtid_state_validator && verbose >= 3) + for (size_t k= 0; k < glev->count; k++) + gtid_state_validator->verify_gtid_state(stderr, &(glev->list[k])); + + was_first_glle_processed= TRUE; + } + + if (ev_type == GTID_EVENT) + { + rpl_gtid ev_gtid; + Gtid_log_event *gle= (Gtid_log_event*) ev; + ev_gtid= {gle->domain_id, gle->server_id, gle->seq_no}; + + /* + If the binlog output should be filtered using GTIDs, test the new event + group to see if its events should be ignored. + */ + if (gtid_event_filter) + { + if (gtid_event_filter->has_finished()) + { + retval= OK_STOP; + goto end; + } + + if (!gtid_event_filter->exclude(&ev_gtid)) + print_event_info->activate_current_event_group(); + else + print_event_info->deactivate_current_event_group(); + } + + /* + Where we always ensure the initial binlog state is valid, we only + continually monitor the GTID stream for validity if we are in GTID + strict mode (for errors) or if three levels of verbosity is provided + (for warnings). + + If we don't care about ensuring GTID validity, just delete the auditor + object to disable it for future checks. + */ + if (gtid_state_validator && print_event_info->is_event_group_active()) + { + if (!(opt_gtid_strict_mode || verbose >= 3)) + { + delete gtid_state_validator; + + /* + Explicitly reset to NULL to simplify checks on if auditing is enabled + i.e. if it is defined, assume we want to use it + */ + gtid_state_validator= NULL; + } + else + { + gtid_err= gtid_state_validator->record(&ev_gtid); + if (gtid_err && opt_gtid_strict_mode) + { + gtid_state_validator->report(stderr, opt_gtid_strict_mode); + goto err; + } + } + } + } + + /* + If the GTID is ignored, it shouldn't count towards offset (rec_count should + not be incremented) + */ + if (!print_event_info->is_event_group_active()) + goto end_skip_count; + + /* + Format events are not concerned by --offset and such, we always need to + read them to be able to process the wanted events. + */ + if (((rec_count >= offset) && + (ev->when >= start_datetime)) || + (ev_type == FORMAT_DESCRIPTION_EVENT)) + { + if (ev_type != FORMAT_DESCRIPTION_EVENT) + { + /* + We have found an event after start_datetime, from now on print + everything (in case the binlog has timestamps increasing and + decreasing, we do this to avoid cutting the middle). + */ + start_datetime= 0; + offset= 0; // print everything and protect against cycling rec_count + /* + Skip events according to the --server-id flag. However, don't + skip format_description or rotate events, because they they + are really "global" events that are relevant for the entire + binlog, even if they have a server_id. Also, we have to read + the format_description event so that we can parse subsequent + events. + */ + if (ev_type != ROTATE_EVENT && is_server_id_excluded(ev->server_id)) + goto end; + } + if ((ev->when >= stop_datetime) + || (pos >= stop_position_mot)) + { + /* end the program */ + retval= OK_STOP; + goto end; + } + if (print_row_event_positions) + fprintf(result_file, "# at %s\n",llstr(pos,ll_buff)); + + if (!opt_hexdump) + print_event_info->hexdump_from= 0; /* Disabled */ + else + print_event_info->hexdump_from= pos; + + print_event_info->base64_output_mode= opt_base64_output_mode; + print_event_info->print_table_metadata= opt_print_table_metadata; + + DBUG_PRINT("debug", ("event_type: %s", ev->get_type_str())); + + switch (ev_type) { + case QUERY_EVENT: + case QUERY_COMPRESSED_EVENT: + { + Query_log_event *qe= (Query_log_event*)ev; + if (!qe->is_trans_keyword()) + { + if (shall_skip_database(qe->db)) + goto end; + } + else + { + /* + In case the event for one of these statements is obtained + from binary log 5.0, make it compatible with 5.1 + */ + qe->flags|= LOG_EVENT_SUPPRESS_USE_F; + } + print_use_stmt(print_event_info, qe); + print_skip_replication_statement(print_event_info, ev); + if (ev->print(result_file, print_event_info)) + goto err; + if (head->error == -1) + goto err; + break; + } + + case CREATE_FILE_EVENT: + { + Create_file_log_event* ce= (Create_file_log_event*)ev; + /* + We test if this event has to be ignored. If yes, we don't save + this event; this will have the good side-effect of ignoring all + related Append_block and Exec_load. + Note that Load event from 3.23 is not tested. + */ + if (shall_skip_database(ce->db)) + goto end; // Next event + /* + We print the event, but with a leading '#': this is just to inform + the user of the original command; the command we want to execute + will be a derivation of this original command (we will change the + filename and use LOCAL), prepared in the 'case EXEC_LOAD_EVENT' + below. + */ + print_skip_replication_statement(print_event_info, ev); + if (ce->print(result_file, print_event_info, TRUE)) + goto err; + // If this binlog is not 3.23 ; why this test?? + if (glob_description_event->binlog_version >= 3) + { + /* + transfer the responsibility for destroying the event to + load_processor + */ + ev= NULL; + if ((retval= load_processor.process(ce)) != OK_CONTINUE) + goto end; + } + break; + } + + case APPEND_BLOCK_EVENT: + /* + Append_block_log_events can safely print themselves even if + the subsequent call load_processor.process fails, because the + output of Append_block_log_event::print is only a comment. + */ + if (ev->print(result_file, print_event_info)) + goto err; + if ((retval= load_processor.process((Append_block_log_event*) ev)) != + OK_CONTINUE) + goto end; + break; + + case EXEC_LOAD_EVENT: + { + if (ev->print(result_file, print_event_info)) + goto err; + Execute_load_log_event *exv= (Execute_load_log_event*)ev; + Create_file_log_event *ce= load_processor.grab_event(exv->file_id); + /* + if ce is 0, it probably means that we have not seen the Create_file + event (a bad binlog, or most probably --start-position is after the + Create_file event). Print a warning comment. + */ + if (ce) + { + bool error; + /* + We must not convert earlier, since the file is used by + my_open() in Load_log_processor::append(). + */ + convert_path_to_forward_slashes((char*) ce->fname); + error= ce->print(result_file, print_event_info, TRUE); + my_free((void*)ce->fname); + delete ce; + if (error) + goto err; + } + else + warning("Ignoring Execute_load_log_event as there is no " + "Create_file event for file_id: %u", exv->file_id); + break; + } + case FORMAT_DESCRIPTION_EVENT: + delete glob_description_event; + glob_description_event= (Format_description_log_event*) ev; + destroy_evt= 0; + print_event_info->common_header_len= + glob_description_event->common_header_len; + if (ev->print(result_file, print_event_info)) + goto err; + if (!remote_opt) + { + ev->free_temp_buf(); // free memory allocated in dump_local_log_entries + } + else + { + /* + disassociate but not free dump_remote_log_entries time memory + */ + ev->temp_buf= 0; + } + /* + We don't want this event to be deleted now, so let's hide it (I + (Guilhem) should later see if this triggers a non-serious Valgrind + error). Not serious error, because we will free description_event + later. + */ + ev= 0; + if (!force_if_open_opt && + (glob_description_event->flags & LOG_EVENT_BINLOG_IN_USE_F)) + { + error("Attempting to dump binlog '%s', which was not closed properly. " + "Most probably, mariadbd is still writing it, or it crashed. " + "Rerun with --force-if-open to ignore this problem.", logname); + DBUG_RETURN(ERROR_STOP); + } + break; + case BEGIN_LOAD_QUERY_EVENT: + if (ev->print(result_file, print_event_info)) + goto err; + if ((retval= load_processor.process((Begin_load_query_log_event*) ev)) != + OK_CONTINUE) + goto end; + break; + case EXECUTE_LOAD_QUERY_EVENT: + { + Execute_load_query_log_event *exlq= (Execute_load_query_log_event*)ev; + char *fname= load_processor.grab_fname(exlq->file_id); + + if (!shall_skip_database(exlq->db)) + { + print_use_stmt(print_event_info, exlq); + if (fname) + { + convert_path_to_forward_slashes(fname); + print_skip_replication_statement(print_event_info, ev); + if (exlq->print(result_file, print_event_info, fname)) + { + my_free(fname); + goto err; + } + } + else + warning("Ignoring Execute_load_query since there is no " + "Begin_load_query event for file_id: %u", exlq->file_id); + } + my_free(fname); + break; + } + case ANNOTATE_ROWS_EVENT: + if (!opt_skip_annotate_row_events) + { + /* + We don't print Annotate event just now because all subsequent + rbr-events can be filtered away. Instead we'll keep the event + till it will be printed together with the first not filtered + away Table map or the last rbr will be processed. + */ + keep_annotate_event((Annotate_rows_log_event*) ev); + destroy_evt= FALSE; + } + break; + case TABLE_MAP_EVENT: + { + Table_map_log_event *map= ((Table_map_log_event *)ev); + if (shall_skip_database(map->get_db_name()) || + shall_skip_table(map->get_table_name())) + { + print_event_info->m_table_map_ignored.set_table(map->get_table_id(), map); + destroy_evt= FALSE; + goto end; + } +#ifdef WHEN_FLASHBACK_REVIEW_READY + /* Create review table for Flashback */ + if (opt_flashback_review) + { + // Check if the table was already created? + Table_map_log_event *exist_table; + exist_table= print_event_info->m_table_map.get_table(map->get_table_id()); + + if (!exist_table) + { + + MYSQL *conn; + MYSQL_RES *res; + MYSQL_ROW row; + char tmp_sql[8096]; + int tmp_sql_offset; + + conn = mysql_init(NULL); + if (!mysql_real_connect(conn, host, user, pass, + map->get_db_name(), port, sock, 0)) + { + fprintf(stderr, "%s\n", mysql_error(conn)); + exit(1); + } + + if (mysql_query(conn, "SET group_concat_max_len=10000;")) + { + fprintf(stderr, "%s\n", mysql_error(conn)); + exit(1); + } + + memset(tmp_sql, 0, sizeof(tmp_sql)); + sprintf(tmp_sql, " " + "SELECT Group_concat(cols) " + "FROM (SELECT 'op_type char(1)' cols " + " UNION ALL " + " SELECT Concat('`', column_name, '_old` ', column_type, ' ', " + " IF(character_set_name IS NOT NULL, " + " Concat('character set ', character_set_name, ' '), ' '), " + " IF(collation_name IS NOT NULL, " + " Concat('collate ', collation_name, ' '), ' ')) cols " + " FROM information_schema.columns " + " WHERE table_schema = '%s' " + " AND table_name = '%s' " + " UNION ALL " + " SELECT Concat('`', column_name, '_new` ', column_type, ' ', " + " IF(character_set_name IS NOT NULL, " + " Concat('character set ', character_set_name, ' '), ' '), " + " IF(collation_name IS NOT NULL, " + " Concat('collate ', collation_name, ' '), ' ')) cols " + " FROM information_schema.columns " + " WHERE table_schema = '%s' " + " AND table_name = '%s') tmp;", + map->get_db_name(), map->get_table_name(), + map->get_db_name(), map->get_table_name()); + + if (mysql_query(conn, tmp_sql)) + { + fprintf(stderr, "%s\n", mysql_error(conn)); + exit(1); + } + res = mysql_use_result(conn); + if ((row = mysql_fetch_row(res)) != NULL) // only one row + { + if (flashback_review_dbname) + { + ev->set_flashback_review_dbname(flashback_review_dbname); + } + else + { + ev->set_flashback_review_dbname(map->get_db_name()); + } + if (flashback_review_tablename) + { + ev->set_flashback_review_tablename(flashback_review_tablename); + } + else + { + memset(tmp_sql, 0, sizeof(tmp_sql)); + sprintf(tmp_sql, "__%s", map->get_table_name()); + ev->set_flashback_review_tablename(tmp_sql); + } + memset(tmp_sql, 0, sizeof(tmp_sql)); + tmp_sql_offset= sprintf(tmp_sql, "CREATE TABLE IF NOT EXISTS"); + tmp_sql_offset+= sprintf(tmp_sql + tmp_sql_offset, " `%s`.`%s` (%s) %s", + ev->get_flashback_review_dbname(), + ev->get_flashback_review_tablename(), + row[0], + print_event_info->delimiter); + } + fprintf(result_file, "%s\n", tmp_sql); + mysql_free_result(res); + mysql_close(conn); + } + else + { + char tmp_str[128]; + + if (flashback_review_dbname) + ev->set_flashback_review_dbname(flashback_review_dbname); + else + ev->set_flashback_review_dbname(map->get_db_name()); + + if (flashback_review_tablename) + ev->set_flashback_review_tablename(flashback_review_tablename); + else + { + memset(tmp_str, 0, sizeof(tmp_str)); + sprintf(tmp_str, "__%s", map->get_table_name()); + ev->set_flashback_review_tablename(tmp_str); + } + } + } +#endif + + /* + The Table map is to be printed, so it's just the time when we may + print the kept Annotate event (if there is any). + print_annotate_event() also deletes the kept Annotate event. + */ + if (print_annotate_event(print_event_info)) + goto err; + + size_t len_to= 0; + const char* db_to= binlog_filter->get_rewrite_db(map->get_db_name(), &len_to); + if (len_to && map->rewrite_db(db_to, len_to, glob_description_event)) + { + error("Could not rewrite database name"); + goto err; + } + if (print_base64(print_event_info, ev)) + goto err; + if (opt_flashback) + reset_dynamic(&events_in_stmt); + break; + } + case WRITE_ROWS_EVENT: + case DELETE_ROWS_EVENT: + case UPDATE_ROWS_EVENT: + case WRITE_ROWS_EVENT_V1: + case UPDATE_ROWS_EVENT_V1: + case DELETE_ROWS_EVENT_V1: + case WRITE_ROWS_COMPRESSED_EVENT: + case DELETE_ROWS_COMPRESSED_EVENT: + case UPDATE_ROWS_COMPRESSED_EVENT: + case WRITE_ROWS_COMPRESSED_EVENT_V1: + case UPDATE_ROWS_COMPRESSED_EVENT_V1: + case DELETE_ROWS_COMPRESSED_EVENT_V1: + { + Rows_log_event *e= (Rows_log_event*) ev; + bool is_stmt_end= e->get_flags(Rows_log_event::STMT_END_F); + if (!print_event_info->found_row_event) + { + print_event_info->found_row_event= 1; + print_event_info->row_events= 0; + } + if (print_row_event(print_event_info, ev, e->get_table_id(), + e->get_flags(Rows_log_event::STMT_END_F))) + goto err; + DBUG_PRINT("info", ("is_stmt_end: %d", (int) is_stmt_end)); + if (is_stmt_end) + print_event_info->found_row_event= 0; + else if (opt_flashback) + destroy_evt= FALSE; + break; + } + case PRE_GA_WRITE_ROWS_EVENT: + case PRE_GA_DELETE_ROWS_EVENT: + case PRE_GA_UPDATE_ROWS_EVENT: + { + Old_rows_log_event *e= (Old_rows_log_event*) ev; + bool is_stmt_end= e->get_flags(Rows_log_event::STMT_END_F); + if (print_row_event(print_event_info, ev, e->get_table_id(), + e->get_flags(Old_rows_log_event::STMT_END_F))) + goto err; + DBUG_PRINT("info", ("is_stmt_end: %d", (int) is_stmt_end)); + if (!is_stmt_end && opt_flashback) + destroy_evt= FALSE; + break; + } + case START_ENCRYPTION_EVENT: + glob_description_event->start_decryption((Start_encryption_log_event*)ev); + /* fall through */ + default: + print_skip_replication_statement(print_event_info, ev); + if (ev->print(result_file, print_event_info)) + goto err; + } + } + + goto end; + +err: + retval= ERROR_STOP; +end: + rec_count++; +end_skip_count: + + DBUG_PRINT("info", ("end event processing")); + /* + Destroy the log_event object. + MariaDB MWL#36: mainline does this: + If reading from a remote host, + set the temp_buf to NULL so that memory isn't freed twice. + We no longer do that, we use Rpl_filter::event_owns_temp_buf instead. + */ + if (ev) + { + /* Holding event output if needed */ + if (!ev->output_buf.is_empty()) + { + LEX_STRING tmp_str; + + tmp_str.length= ev->output_buf.length(); + tmp_str.str= ev->output_buf.release(); + + if (opt_flashback) + { + if (ev_type == STOP_EVENT) + stop_event_string.reset(tmp_str.str, tmp_str.length, tmp_str.length, + &my_charset_bin); + else + { + if (insert_dynamic(&binlog_events, (uchar *) &tmp_str)) + { + error("Out of memory: can't allocate memory to store the flashback events."); + exit(1); + } + } + } + else + { + my_fwrite(result_file, (const uchar *) tmp_str.str, tmp_str.length, + MYF(MY_NABP)); + fflush(result_file); + my_free(tmp_str.str); + } + } + + if (destroy_evt) /* destroy it later if not set (ignored table map) */ + delete ev; + } + DBUG_PRINT("exit",("return: %d", retval)); + DBUG_RETURN(retval); +} + + +static struct my_option my_options[] = +{ + {"help", '?', "Display this help and exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"base64-output", OPT_BASE64_OUTPUT_MODE, + /* 'unspec' is not mentioned because it is just a placeholder. */ + "Determine when the output statements should be base64-encoded BINLOG " + "statements: " + "‘never’ neither prints base64 encodings nor verbose event data, and " + "will exit on error if a row-based event is found. " + "'decode-rows' decodes row events into commented SQL statements if the " + "--verbose option is also given. " + "‘auto’ outputs base64 encoded entries for row-based and format " + "description events. " + "If no option is given at all, the default is ‘auto', and is " + "consequently the only option that should be used when row-format events " + "are processed for re-execution.", + &opt_base64_output_mode_str, &opt_base64_output_mode_str, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + /* + mysqlbinlog needs charsets knowledge, to be able to convert a charset + number found in binlog to a charset name (to be able to print things + like this: + SET @`a`:=_cp850 0x4DFC6C6C6572 COLLATE `cp850_general_ci`; + */ + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", &charsets_dir, + &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'd', "List entries for just this database (local log only).", + &database, &database, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, +#ifndef DBUG_OFF + {"debug", '#', "Output debug log.", ¤t_dbug_option, + ¤t_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit .", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"disable-log-bin", 'D', "Disable binary log. This is useful, if you " + "enabled --to-last-log and are sending the output to the same MariaDB server. " + "This way you could avoid an endless loop. You would also like to use it " + "when restoring after a crash to avoid duplication of the statements you " + "already have. NOTE: you will need a SUPER privilege to use this option.", + &disable_log_bin, &disable_log_bin, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"flashback", 'B', "Flashback feature can rollback you committed data to a special time point.", +#ifdef WHEN_FLASHBACK_REVIEW_READY + "before Flashback feature writing a row, original row can insert to review-dbname.review-tablename," + "and mysqlbinlog will login mysql by user(-u) and password(-p) and host(-h).", +#endif + &opt_flashback, &opt_flashback, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"force-if-open", 'F', "Force if binlog was not closed properly.", + &force_if_open_opt, &force_if_open_opt, 0, GET_BOOL, NO_ARG, + 1, 0, 0, 0, 0, 0}, + {"force-read", 'f', "Force reading unknown binlog events.", + &force_opt, &force_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"hexdump", 'H', "Augment output with hexadecimal and ASCII event dump.", + &opt_hexdump, &opt_hexdump, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Get the binlog from server.", &host, &host, + 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"local-load", 'l', "Prepare local temporary files for LOAD DATA INFILE in the specified directory.", + &dirname_for_local_load, &dirname_for_local_load, 0, + GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"offset", 'o', "Skip the first N entries.", &offset, &offset, + 0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', "Password to connect to remote server.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugindir, &opt_plugindir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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) ").", + &port, &port, 0, GET_INT, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"read-from-remote-server", 'R', "Read binary logs from a MariaDB server.", + &remote_opt, &remote_opt, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"raw", 0, "Requires -R. Output raw binlog data instead of SQL " + "statements. Output files named after server logs.", + &opt_raw_mode, &opt_raw_mode, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"result-file", 'r', "Direct output to a given file. With --raw this is a " + "prefix for the file names.", + &result_file_name, &result_file_name, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, +#ifdef WHEN_FLASHBACK_REVIEW_READY + {"review", opt_flashback_review, "Print review sql in output file.", + &opt_flashback_review, &opt_flashback_review, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"review-dbname", opt_flashback_flashback_review_dbname, + "Writing flashback original row data into this db", + &flashback_review_dbname, &flashback_review_dbname, + 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"review-tablename", opt_flashback_flashback_review_tablename, + "Writing flashback original row data into this table", + &flashback_review_tablename, &flashback_review_tablename, + 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"print-row-count", OPT_PRINT_ROW_COUNT, + "Print row counts for each row events", + &print_row_count, &print_row_count, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, + 0, 0}, + {"print-row-event-positions", OPT_PRINT_ROW_EVENT_POSITIONS, + "Print row event positions", + &print_row_event_positions, &print_row_event_positions, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"ignore-domain-ids", OPT_IGNORE_DOMAIN_IDS, + "A list of positive integers, separated by commas, that form a blacklist " + "of domain ids. Any log event with a GTID that originates from a domain id " + "specified in this list is hidden. Cannot be used with " + "--do-domain-ids. When used with --(ignore|do)-server-ids, the result is the " + "intersection between the two datasets.", + &ignore_domain_ids_str, &ignore_domain_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"do-domain-ids", OPT_DO_DOMAIN_IDS, + "A list of positive integers, separated by commas, that form a whitelist " + "of domain ids. Any log event with a GTID that originates from a domain id " + "specified in this list is displayed. Cannot be used with " + "--ignore-domain-ids. When used with --(ignore|do)-server-ids, the result " + "is the intersection between the two datasets.", + &do_domain_ids_str, &do_domain_ids_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, + 0, 0, 0, 0, 0}, + {"ignore-server-ids", OPT_IGNORE_SERVER_IDS, + "A list of positive integers, separated by commas, that form a blacklist " + "of server ids. Any log event originating from a server id " + "specified in this list is hidden. Cannot be used with " + "--do-server-ids. When used with --(ignore|do)-domain-ids, the result is " + "the intersection between the two datasets.", + &ignore_server_ids_str, &ignore_server_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"do-server-ids", OPT_DO_SERVER_IDS, + "A list of positive integers, separated by commas, that form a whitelist " + "of server ids. Any log event originating from a server id " + "specified in this list is displayed. Cannot be used with " + "--ignore-server-ids. When used with --(ignore|do)-domain-ids, the result " + "is the intersection between the two datasets. Alias for --server-id.", + &do_server_ids_str, &do_server_ids_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"server-id", OPT_SERVER_ID, + "Extract only binlog entries created by the server having the given id. " + "Alias for --do-server-ids.", + &server_id_str, &server_id_str, 0, GET_STR_ALLOC, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"set-charset", OPT_SET_CHARSET, + "Add 'SET NAMES character_set' to the output.", &charset, + &charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"short-form", 's', "Just show regular queries: no extra info, no " + "row-based events and no row counts. This is mainly for testing only, " + "and should not be used to feed to the MariaDB server. " + "If you want to just suppress base64-output, you can instead " + "use --base64-output=never", + &short_form, &short_form, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &sock, &sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, + 0, 0}, +#include + {"start-datetime", OPT_START_DATETIME, + "Start reading the binlog at first event having a datetime equal or " + "posterior to the argument; the argument must be a date and time " + "in the local time zone, in any format accepted by the MariaDB server " + "for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 " + "(you should probably use quotes for your shell to set it properly).", + &start_datetime_str, &start_datetime_str, + 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"start-position", 'j', + "Start reading the binlog at this position. Type can either be a positive " + "integer or a GTID list. When using a positive integer, the value only " + "applies to the first binlog passed on the command line. In GTID mode, " + "multiple GTIDs can be passed as a comma separated list, where each must " + "have a unique domain id. The list represents the gtid binlog state that " + "the client (another \"replica\" server) is aware of. Therefore, each GTID " + "is exclusive; only events after a given sequence number will be printed to " + "allow users to receive events after their current state.", + &start_pos_str, &start_pos_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"gtid-strict-mode", 0, "Process binlog according to gtid-strict-mode " + "specification. The start, stop positions are verified to satisfy " + "start < stop comparison condition. Sequence numbers of any gtid domain " + "must comprise monotically growing sequence", + &opt_gtid_strict_mode, &opt_gtid_strict_mode, 0, + GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"stop-datetime", OPT_STOP_DATETIME, + "Stop reading the binlog at first event having a datetime equal or " + "posterior to the argument; the argument must be a date and time " + "in the local time zone, in any format accepted by the MariaDB server " + "for DATETIME and TIMESTAMP types, for example: 2004-12-25 11:25:56 " + "(you should probably use quotes for your shell to set it properly).", + &stop_datetime_str, &stop_datetime_str, + 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"stop-never", 0, "Wait for more data from the server " + "instead of stopping at the end of the last log. Implies --to-last-log.", + &opt_stop_never, &opt_stop_never, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"stop-never-slave-server-id", 0, + "The slave server_id used for --read-from-remote-server --stop-never.", + &opt_stop_never_slave_server_id, &opt_stop_never_slave_server_id, 0, + GET_ULONG, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"stop-position", OPT_STOP_POSITION, + "Stop reading the binlog at this position. Type can either be a positive " + "integer or a GTID list. When using a positive integer, the value only " + "applies to the last binlog passed on the command line. In GTID mode, " + "multiple GTIDs can be passed as a comma separated list, where each must " + "have a unique domain id. Each GTID is inclusive; only events up to the " + "given sequence numbers are printed.", + &stop_pos_str, &stop_pos_str, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, + 0, 0}, + {"table", 'T', "List entries for just this table (affects only row events).", + &table, &table, 0, GET_STR_ALLOC, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"to-last-log", 't', "Requires -R. Will not stop at the end of the \ +requested binlog but rather continue printing until the end of the last \ +binlog of the MariaDB server. If you send the output to the same MariaDB server, \ +that may lead to an endless loop.", + &to_last_remote_log, &to_last_remote_log, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"user", 'u', "Connect to the remote server as username.", + &user, &user, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, + 0, 0}, + {"verbose", 'v', "Reconstruct SQL statements out of row events. " + "-v -v adds comments on column data types. " + "-v -v -v adds diagnostic warnings about event " + "integrity before program exit.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Print version and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"open_files_limit", OPT_OPEN_FILES_LIMIT, + "Used to reserve file descriptors for use by this program.", + &open_files_limit, &open_files_limit, 0, GET_ULONG, + REQUIRED_ARG, MY_NFILE, 8, OS_FILE_LIMIT, 0, 1, 0}, + {"binlog-row-event-max-size", 0, + "The maximum size of a row-based binary log event in bytes. Rows will be " + "grouped into events smaller than this size if possible. " + "This value must be a multiple of 256.", + &opt_binlog_rows_event_max_size, &opt_binlog_rows_event_max_size, 0, + GET_ULONG, REQUIRED_ARG, UINT_MAX, 256, ULONG_MAX, 0, 256, 0}, +#ifndef DBUG_OFF + {"debug-binlog-row-event-max-encoded-size", 0, + "The maximum size of base64-encoded rows-event in one BINLOG pseudo-query " + "instance. When the computed actual size exceeds the limit " + "the BINLOG's argument string is fragmented in two.", + &opt_binlog_rows_event_max_encoded_size, + &opt_binlog_rows_event_max_encoded_size, 0, + GET_ULONG, REQUIRED_ARG, UINT_MAX/4, 256, ULONG_MAX, 0, 256, 0}, +#endif + {"verify-binlog-checksum", 'c', "Verify binlog event checksums.", + (uchar**) &opt_verify_binlog_checksum, (uchar**) &opt_verify_binlog_checksum, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"rewrite-db", OPT_REWRITE_DB, + "Updates to a database with a different name than the original. \ +Example: rewrite-db='from->to'.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"skip-annotate-row-events", OPT_SKIP_ANNOTATE_ROWS_EVENTS, + "Don't print Annotate_rows events stored in the binary log.", + (uchar**) &opt_skip_annotate_row_events, + (uchar**) &opt_skip_annotate_row_events, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"print-table-metadata", OPT_PRINT_TABLE_METADATA, + "Print metadata stored in Table_map_log_event", + &opt_print_table_metadata, &opt_print_table_metadata, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +/** + Auxiliary function used by error() and warning(). + + Prints the given text (normally "WARNING: " or "ERROR: "), followed + by the given vprintf-style string, followed by a newline. + + @param format Printf-style format string. + @param args List of arguments for the format string. + @param msg Text to print before the string. +*/ +static void error_or_warning(const char *format, va_list args, const char *msg) +{ + if (result_file) + fflush(result_file); + fprintf(stderr, "%s: ", msg); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); + fflush(stderr); +} + +/** + Prints a message to stderr, prefixed with the text "ERROR: " and + suffixed with a newline. + + @param format Printf-style format string, followed by printf + varargs. +*/ +static void error(const char *format,...) +{ + va_list args; + va_start(args, format); + error_or_warning(format, args, "ERROR"); + va_end(args); +} + + +/** + This function is used in log_event.cc to report errors. + + @param format Printf-style format string, followed by printf + varargs. +*/ +static void sql_print_error(const char *format,...) +{ + va_list args; + va_start(args, format); + error_or_warning(format, args, "ERROR"); + va_end(args); +} + +/** + Prints a message to stderr, prefixed with the text "WARNING: " and + suffixed with a newline. + + @param format Printf-style format string, followed by printf + varargs. +*/ +static void warning(const char *format,...) +{ + va_list args; + va_start(args, format); + error_or_warning(format, args, "WARNING"); + va_end(args); +} + +/** + Frees memory for global variables in this file. +*/ +static void cleanup() +{ + DBUG_ENTER("cleanup"); + my_free(pass); + my_free(database); + my_free(table); + my_free(host); + my_free(user); + my_free(const_cast(dirname_for_local_load)); + my_free(start_datetime_str); + my_free(stop_datetime_str); + my_free(start_pos_str); + my_free(stop_pos_str); + my_free(ignore_domain_ids_str); + my_free(do_domain_ids_str); + my_free(ignore_server_ids_str); + my_free(do_server_ids_str); + my_free(server_id_str); + free_root(&glob_root, MYF(0)); + + if (gtid_event_filter) + { + delete gtid_event_filter; + } + else + { + /* + If there was an error during input parsing, gtid_event_filter will not + be set, so we need to ensure the comprising filters are cleaned up + properly. + */ + if (domain_id_gtid_filter) + delete domain_id_gtid_filter; + if (position_gtid_filter) + delete position_gtid_filter; + if (server_id_gtid_filter) + delete server_id_gtid_filter; + } + if (gtid_state_validator) + delete gtid_state_validator; + + delete binlog_filter; + delete glob_description_event; + if (mysql) + mysql_close(mysql); + free_defaults(defaults_argv); + free_annotate_event(); + my_free_open_file_info(); + load_processor.destroy(); + mysql_server_end(); + if (opt_flashback) + { + delete_dynamic(&binlog_events); + delete_dynamic(&events_in_stmt); + } + DBUG_VOID_RETURN; +} + +/* + Parse a list of positive numbers separated by commas. + Returns a list of numbers on success, NULL on parsing/resource error +*/ +static uint32 *parse_u32_list(const char *str, size_t str_len, uint32 *n_vals) +{ + const char *str_begin= const_cast(str); + const char *str_end= str_begin + str_len; + const char *p = str_begin; + uint32 len= 0, alloc_len= (uint32) ceil(str_len/2.0); + uint32 *list= NULL; + int err; + + for (;;) + { + uint32 val; + + /* + Set it to the end of the string overall, but when parsing, it will be + moved to the end of the element + */ + char *el_end= (char*) str_begin + str_len; + + if (len >= (((uint32)1 << 28)-1)) + { + my_free(list); + list= NULL; + goto end; + } + + val= (uint32)my_strtoll10(p, &el_end, &err); + if (err) + { + my_free(list); + list= NULL; + goto end; + } + p = el_end; + + if ((!list || len >= alloc_len) && + !(list= + (uint32 *)my_realloc(PSI_INSTRUMENT_ME, list, + (alloc_len= alloc_len*2) * sizeof(uint32), + MYF(MY_FREE_ON_ERROR|MY_ALLOW_ZERO_PTR)))) + return NULL; + list[len++]= val; + + if (el_end == str_end) + break; + if (*p != ',') + { + my_free(list); + return NULL; + } + ++p; + } + *n_vals= len; + +end: + return list; +} + +/* + If multiple different types of Gtid_event_filters are used, the result + should be the intersection between the filter types. +*/ +static void extend_main_gtid_event_filter(Gtid_event_filter *new_filter) +{ + if (gtid_event_filter == NULL) + { + gtid_event_filter= new_filter; + } + else + { + if (gtid_event_filter->get_filter_type() != + Gtid_event_filter::INTERSECTING_GTID_FILTER_TYPE) + gtid_event_filter= + new Intersecting_gtid_event_filter(gtid_event_filter, new_filter); + else + ((Intersecting_gtid_event_filter *) gtid_event_filter) + ->add_filter(new_filter); + } +} + +static void die(int err) +{ + cleanup(); + my_end(MY_DONT_FREE_DBUG); + exit(err); +} + + +static void print_version() +{ + printf("%s Ver 3.5 for %s at %s\n", my_progname, SYSTEM_TYPE, MACHINE_TYPE); +} + + +static void usage() +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("\ +Dumps a MariaDB binary log in a format usable for viewing or for piping to\n\ +the mysql command line client.\n\n"); + printf("Usage: %s [options] log-files\n", my_progname); + print_defaults("my",load_groups); + puts(""); + my_print_help(my_options); + my_print_variables(my_options); +} + + +static my_time_t convert_str_to_timestamp(const char* str) +{ + MYSQL_TIME_STATUS status; + MYSQL_TIME l_time; + long dummy_my_timezone; + uint dummy_in_dst_time_gap; + + /* We require a total specification (date AND time) */ + if (str_to_datetime_or_date(str, (uint) strlen(str), &l_time, 0, &status) || + l_time.time_type != MYSQL_TIMESTAMP_DATETIME || status.warnings) + { + error("Incorrect date and time argument: %s", str); + die(1); + } + /* + Note that Feb 30th, Apr 31st cause no error messages and are mapped to + the next existing day, like in mysqld. Maybe this could be changed when + mysqld is changed too (with its "strict" mode?). + */ + return + my_system_gmt_sec(&l_time, &dummy_my_timezone, &dummy_in_dst_time_gap); +} + +/** + Parses a start or stop position argument and populates either + start_position/stop_position (if a log offset) or position_gtid_filter + (if a gtid position) + + @param[in] option_name : Name of the command line option provided (used for + error message) + @param[in] option_val : The user-provided value of the option_name + @param[out] fallback : Pointer to a global variable to set if using log + offsets + @param[in] add_gtid : Function pointer to a class method to add a GTID to a + Gtid_event_filter + @param[in] add_zero_seqno : If using GTID positions, this boolean specifies + if GTIDs with a sequence number of 0 should be added to the filter +*/ +int parse_position_argument( + const char *option_name, char *option_val, ulonglong *fallback, + int (Domain_gtid_event_filter::*add_gtid)(rpl_gtid *), + my_bool add_zero_seqno) +{ + uint32 n_gtids= 0; + rpl_gtid *gtid_list= + gtid_parse_string_to_list(option_val, strlen(option_val), &n_gtids); + + if (gtid_list == NULL) + { + int err= 0; + char *end_ptr= NULL; + /* + No GTIDs specified in position specification. Treat the value + as a singular index. + */ + *fallback= my_strtoll10(option_val, &end_ptr, &err); + + if (err || *end_ptr) + { + // Can't parse the position from the user + sql_print_error("%s argument value is invalid. Should be either a " + "positive integer or GTID.", + option_name); + return 1; + } + } + else if (n_gtids > 0) + { + uint32 gtid_idx; + + if (position_gtid_filter == NULL) + position_gtid_filter= new Domain_gtid_event_filter(); + + for (gtid_idx = 0; gtid_idx < n_gtids; gtid_idx++) + { + rpl_gtid *gtid= >id_list[gtid_idx]; + if ((gtid->seq_no || add_zero_seqno) && + (position_gtid_filter->*add_gtid)(gtid)) + { + my_free(gtid_list); + return 1; + } + } + my_free(gtid_list); + } + else + { + DBUG_ASSERT(0); + } + return 0; +} + +/** + Parses a do/ignore domain/server ids option and populates the corresponding + gtid filter + + @param[in] option_name : Name of the command line option provided (used for + error message) + @param[in] option_value : The user-provided list of domain or server ids + @param[in] filter : The filter to update with the provided domain/server id + @param[in] mode : Specifies whether the list should be a blacklist or + whitelist +*/ +template +int parse_gtid_filter_option( + const char *option_name, char *option_val, T **filter, + Gtid_event_filter::id_restriction_mode mode) +{ + uint32 n_ids= 0; + uint32 *id_list= parse_u32_list(option_val, strlen(option_val), &n_ids); + + if (id_list == NULL) + { + DBUG_ASSERT(n_ids == 0); + sql_print_error( + "Input for %s is invalid. Should be a list of positive integers", + option_name); + return 1; + } + + if (!(*filter)) + (*filter)= new T(); + + int err= (*filter)->set_id_restrictions(id_list, n_ids, mode); + my_free(id_list); + return err; +} + +extern "C" my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + bool tty_password=0; + + switch (opt->id) { +#ifndef DBUG_OFF + case '#': + if (!argument) + argument= (char*) default_dbug_option; + current_dbug_option= argument; + DBUG_PUSH(argument); + break; +#endif +#include + case 'B': + opt_flashback= 1; + break; + case 'd': + one_database = 1; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; // Don't require password + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + my_free(pass); + char *start= (char*) argument; + pass= my_strdup(PSI_NOT_INSTRUMENTED, argument,MYF(MY_FAE)); + while (*argument) + *(char*)argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]=0; /* Cut length of argument */ + } + else + tty_password=1; + break; + case 'R': + remote_opt= 1; + break; + case 'T': + one_table= 1; + break; + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + die(1); + } + break; +#ifdef WHEN_FLASHBACK_REVIEW_READY + case opt_flashback_review: + opt_flashback_review= 1; + break; +#endif + case OPT_START_DATETIME: + start_datetime= convert_str_to_timestamp(start_datetime_str); + break; + case OPT_STOP_DATETIME: + stop_datetime= convert_str_to_timestamp(stop_datetime_str); + break; + case OPT_BASE64_OUTPUT_MODE: + int val; + + if ((val= find_type_with_warning(argument, &base64_output_mode_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + die(1); + } + opt_base64_output_mode= (enum_base64_output_mode)(val - 1); + break; + case OPT_REWRITE_DB: // db_from->db_to + { + /* See also handling of OPT_REPLICATE_REWRITE_DB in sql/mysqld.cc */ + if (binlog_filter->add_rewrite_db(argument)) + { + sql_print_error("Bad syntax in rewrite-db. Expected syntax is FROM->TO."); + return 1; + } + break; + } + case OPT_PRINT_ROW_COUNT: + print_row_count_used= 1; + break; + case OPT_PRINT_ROW_EVENT_POSITIONS: + print_row_event_positions_used= 1; + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + case 'v': + if (argument == disabled_my_option) + verbose= 0; + else + verbose++; + break; + case 'V': + print_version(); + opt_version= 1; + break; + case OPT_STOP_POSITION: + { + /* Stop position was already specified, so reset it and use the new list */ + if (position_gtid_filter && + position_gtid_filter->get_num_stop_gtids() > 0) + position_gtid_filter->clear_stop_gtids(); + + if (parse_position_argument( + "--stop-position", stop_pos_str, &stop_position, + &Domain_gtid_event_filter::add_stop_gtid, TRUE)) + return 1; + break; + } + case 'j': + { + /* Start position was already specified, so reset it and use the new list */ + if (position_gtid_filter && + position_gtid_filter->get_num_start_gtids() > 0) + position_gtid_filter->clear_start_gtids(); + + if (parse_position_argument( + "--start-position", start_pos_str, &start_position, + &Domain_gtid_event_filter::add_start_gtid, FALSE)) + return 1; + break; + } + case OPT_IGNORE_DOMAIN_IDS: + { + if (parse_gtid_filter_option( + "--ignore-domain-ids", ignore_domain_ids_str, + &domain_id_gtid_filter, + Gtid_event_filter::id_restriction_mode::BLACKLIST_MODE)) + return 1; + break; + } + case OPT_DO_DOMAIN_IDS: + { + if (parse_gtid_filter_option( + "--do-domain-ids", do_domain_ids_str, + &domain_id_gtid_filter, + Gtid_event_filter::id_restriction_mode::WHITELIST_MODE)) + return 1; + break; + } + case OPT_IGNORE_SERVER_IDS: + { + if (parse_gtid_filter_option( + "--ignore-server-ids", ignore_server_ids_str, + &server_id_gtid_filter, + Gtid_event_filter::id_restriction_mode::BLACKLIST_MODE)) + return 1; + break; + } + case OPT_DO_SERVER_IDS: + { + if (parse_gtid_filter_option( + "--do-server-ids", do_server_ids_str, + &server_id_gtid_filter, + Gtid_event_filter::id_restriction_mode::WHITELIST_MODE)) + return 1; + break; + } + case OPT_SERVER_ID: + { + if (parse_gtid_filter_option( + "--server-id", server_id_str, + &server_id_gtid_filter, + Gtid_event_filter::id_restriction_mode::WHITELIST_MODE)) + return 1; + break; + } + case '?': + usage(); + opt_version= 1; + break; + } + if (tty_password) + pass= my_get_tty_password(NullS); + + return 0; +} + +static int parse_args(int *argc, char*** argv) +{ + int ho_error; + + if ((ho_error=handle_options(argc, argv, my_options, get_one_option))) + { + die(ho_error); + } + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + else if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + if (start_position > UINT_MAX32 && remote_opt) + { + /* Here we just emulate old behaviour of option limit handling */ + fprintf(stderr, "Warning: option 'start-position': unsigned value %llu " + "adjusted to 4294967295 (limitation of the client-server protocol)", + start_position); + start_position= UINT_MAX32; + } + + /* + Always initialize the stream auditor initially because it is used to check + the initial state of the binary log is correct. If we don't want it later + (i.e. --skip-gtid-strict-mode or -vvv is not given), it is deleted when we + are certain the initial gtid state is set. + */ + gtid_state_validator= new Binlog_gtid_state_validator(); + + if (position_gtid_filter) + { + if (opt_gtid_strict_mode && + position_gtid_filter->validate_window_filters()) + { + /* + In strict mode, if any --start/stop-position GTID ranges are invalid, + quit in error. Note that any specific error messages will have + already been written. + */ + die(1); + } + extend_main_gtid_event_filter(position_gtid_filter); + + /* + GTIDs before a start position shouldn't be validated, so we initialize + the stream auditor to only monitor GTIDs after these positions. + */ + size_t n_start_gtids= position_gtid_filter->get_num_start_gtids(); + rpl_gtid *start_gtids= position_gtid_filter->get_start_gtids(); + gtid_state_validator->initialize_start_gtids(start_gtids, n_start_gtids); + my_free(start_gtids); + } + + if(domain_id_gtid_filter) + extend_main_gtid_event_filter(domain_id_gtid_filter); + + if(server_id_gtid_filter) + extend_main_gtid_event_filter(server_id_gtid_filter); + + return 0; +} + + +/** + Create and initialize the global mysql object, and connect to the + server. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +static Exit_status safe_connect() +{ + my_bool reconnect= 1; + /* Close any old connections to MySQL */ + if (mysql) + mysql_close(mysql); + + mysql= mysql_init(NULL); + + if (!mysql) + { + error("Failed on mysql_init."); + return ERROR_STOP; + } + +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif /*HAVE_OPENSSL*/ + + if (opt_plugindir && *opt_plugindir) + mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugindir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + if (opt_protocol) + mysql_options(mysql, MYSQL_OPT_PROTOCOL, (char*) &opt_protocol); + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqlbinlog"); + if (!mysql_real_connect(mysql, host, user, pass, 0, port, sock, 0)) + { + error("Failed on connect: %s", mysql_error(mysql)); + return ERROR_STOP; + } + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + return OK_CONTINUE; +} + + +/** + High-level function for dumping a named binlog. + + This function calls dump_remote_log_entries() or + dump_local_log_entries() to do the job. + + @param[in] logname Name of input binlog. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. + @retval OK_STOP No error, but the end of the specified range of + events to process has been reached and the program should terminate. +*/ +static Exit_status dump_log_entries(const char* logname) +{ + Exit_status rc; + PRINT_EVENT_INFO print_event_info; + + if (!print_event_info.init_ok()) + return ERROR_STOP; + + if (position_gtid_filter || domain_id_gtid_filter) + print_event_info.enable_event_group_filtering(); + + /* + Set safe delimiter, to dump things + like CREATE PROCEDURE safely + */ + if (!opt_raw_mode) + fprintf(result_file, "DELIMITER /*!*/;\n"); + strmov(print_event_info.delimiter, "/*!*/;"); + + if (short_form) + { + if (!print_row_event_positions_used) + print_row_event_positions= 0; + if (!print_row_count_used) + print_row_count = 0; + } + if (opt_flashback) + { + if (!print_row_event_positions_used) + print_row_event_positions= 0; + } + + print_event_info.verbose= short_form ? 0 : verbose; + print_event_info.short_form= short_form; + print_event_info.print_row_count= print_row_count; + print_event_info.file= result_file; + fflush(result_file); + rc= (remote_opt ? dump_remote_log_entries(&print_event_info, logname) : + dump_local_log_entries(&print_event_info, logname)); + + if (rc == ERROR_STOP) + return rc; + + /* Set delimiter back to semicolon */ + if (!opt_raw_mode && !opt_flashback) + fprintf(result_file, "DELIMITER ;\n"); + strmov(print_event_info.delimiter, ";"); + return rc; +} + + +/** + When reading a remote binlog, this function is used to grab the + Format_description_log_event in the beginning of the stream. + + This is not as smart as check_header() (used for local log); it will + not work for a binlog which mixes format. TODO: fix this. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. +*/ +static Exit_status check_master_version() +{ + MYSQL_RES* res = 0; + MYSQL_ROW row; + uint version; + + if (mysql_query(mysql, "SELECT VERSION()") || + !(res = mysql_store_result(mysql))) + { + error("Could not find server version: " + "Query failed when checking master version: %s", mysql_error(mysql)); + return ERROR_STOP; + } + if (!(row = mysql_fetch_row(res))) + { + error("Could not find server version: " + "Master returned no rows for SELECT VERSION()."); + goto err; + } + + if (!(version = atoi(row[0]))) + { + error("Could not find server version: " + "Master reported NULL for the version."); + goto err; + } + /* + Make a notice to the server that this client + is checksum-aware. It does not need the first fake Rotate + necessary checksummed. + That preference is specified below. + */ + if (mysql_query(mysql, "SET @master_binlog_checksum='NONE'")) + { + error("Could not notify master about checksum awareness." + "Master returned '%s'", mysql_error(mysql)); + goto err; + } + + /* + Announce our capabilities to the server, so it will send us all the events + that we know about. + */ + if (mysql_query(mysql, "SET @mariadb_slave_capability=" + STRINGIFY_ARG(MARIA_SLAVE_CAPABILITY_MINE))) + { + error("Could not inform master about capability. Master returned '%s'", + mysql_error(mysql)); + goto err; + } + + if (position_gtid_filter && + position_gtid_filter->get_num_start_gtids() > 0) + { + char str_buf[256]; + String query_str(str_buf, sizeof(str_buf), system_charset_info); + query_str.length(0); + query_str.append(STRING_WITH_LEN("SET @slave_connect_state='"), + system_charset_info); + + size_t n_start_gtids= position_gtid_filter->get_num_start_gtids(); + rpl_gtid *start_gtids= position_gtid_filter->get_start_gtids(); + + for (size_t gtid_idx = 0; gtid_idx < n_start_gtids; gtid_idx++) + { + char buf[256]; + rpl_gtid *start_gtid= &start_gtids[gtid_idx]; + + sprintf(buf, "%u-%u-%llu", + start_gtid->domain_id, start_gtid->server_id, + start_gtid->seq_no); + query_str.append(buf, strlen(buf)); + if (gtid_idx < n_start_gtids - 1) + query_str.append(','); + } + my_free(start_gtids); + + query_str.append(STRING_WITH_LEN("'"), system_charset_info); + if (unlikely(mysql_real_query(mysql, query_str.ptr(), query_str.length()))) + { + error("Setting @slave_connect_state failed with error: %s", + mysql_error(mysql)); + goto err; + } + } + + delete glob_description_event; + glob_description_event= NULL; + + switch (version) { + case 3: + glob_description_event= new Format_description_log_event(1); + break; + case 4: + glob_description_event= new Format_description_log_event(3); + break; + case 5: + case 10: + /* + The server is soon going to send us its Format_description log + event, unless it is a 5.0 server with 3.23 or 4.0 binlogs. + So we first assume that this is 4.0 (which is enough to read the + Format_desc event if one comes). + */ + glob_description_event= new Format_description_log_event(3); + break; + default: + error("Could not find server version: " + "Master reported unrecognized MariaDB version '%s'.", row[0]); + goto err; + } + if (!glob_description_event || !glob_description_event->is_valid()) + { + error("Failed creating Format_description_log_event; out of memory?"); + goto err; + } + + mysql_free_result(res); + return OK_CONTINUE; + +err: + mysql_free_result(res); + return ERROR_STOP; +} + + +static Exit_status handle_event_text_mode(PRINT_EVENT_INFO *print_event_info, + ulong *len, + const char* logname, + uint logname_len, my_off_t old_off) +{ + const char *error_msg; + Log_event *ev; + NET *net= &mysql->net; + DBUG_ENTER("handle_event_text_mode"); + + if (net->read_pos[5] == ANNOTATE_ROWS_EVENT) + { + if (!(ev= read_remote_annotate_event(net->read_pos + 1, *len - 1, + &error_msg))) + { + error("Could not construct annotate event object: %s", error_msg); + DBUG_RETURN(ERROR_STOP); + } + } + else + { + if (!(ev= Log_event::read_log_event(net->read_pos + 1 , + *len - 1, &error_msg, + glob_description_event, + opt_verify_binlog_checksum))) + { + error("Could not construct log event object: %s", error_msg); + DBUG_RETURN(ERROR_STOP); + } + /* + If reading from a remote host, ensure the temp_buf for the + Log_event class is pointing to the incoming stream. + */ + ev->register_temp_buf(net->read_pos + 1, FALSE); + } + + Log_event_type type= ev->get_type_code(); + if (glob_description_event->binlog_version >= 3 || + (type != LOAD_EVENT && type != CREATE_FILE_EVENT)) + { + /* + If this is a Rotate event, maybe it's the end of the requested binlog; + in this case we are done (stop transfer). + This is suitable for binlogs, not relay logs (but for now we don't read + relay logs remotely because the server is not able to do that). If one + day we read relay logs remotely, then we will have a problem with the + detection below: relay logs contain Rotate events which are about the + binlogs, so which would trigger the end-detection below. + */ + if (type == ROTATE_EVENT) + { + Rotate_log_event *rev= (Rotate_log_event *)ev; + /* + If this is a fake Rotate event, and not about our log, we can stop + transfer. If this a real Rotate event (so it's not about our log, + it's in our log describing the next log), we print it (because it's + part of our log) and then we will stop when we receive the fake one + soon. + */ + if (rev->when == 0) + { + *len= 1; // fake Rotate, so don't increment old_off + if (!to_last_remote_log) + { + if ((rev->ident_len != logname_len) || + memcmp(rev->new_log_ident, logname, logname_len)) + { + delete ev; + DBUG_RETURN(OK_EOF); + } + /* + Otherwise, this is a fake Rotate for our log, at the very + beginning for sure. Skip it, because it was not in the original + log. If we are running with to_last_remote_log, we print it, + because it serves as a useful marker between binlogs then. + */ + delete ev; + DBUG_RETURN(OK_CONTINUE); + } + } + } + else if (type == FORMAT_DESCRIPTION_EVENT) + { + /* + This could be an fake Format_description_log_event that server + (5.0+) automatically sends to a slave on connect, before sending + a first event at the requested position. If this is the case, + don't increment old_off. Real Format_description_log_event always + starts from BIN_LOG_HEADER_SIZE position. + */ + if (old_off != BIN_LOG_HEADER_SIZE) + *len= 1; // fake event, don't increment old_off + } + Exit_status retval= process_event(print_event_info, ev, old_off, logname); + if (retval != OK_CONTINUE) + DBUG_RETURN(retval); + } + else + { + Load_log_event *le= (Load_log_event*)ev; + const char *old_fname= le->fname; + uint old_len= le->fname_len; + File file; + Exit_status retval; + char fname[FN_REFLEN+1]; + + if ((file= load_processor.prepare_new_file_for_old_format(le,fname)) < 0) + { + DBUG_RETURN(ERROR_STOP); + } + + retval= process_event(print_event_info, ev, old_off, logname); + if (retval != OK_CONTINUE) + { + my_close(file,MYF(MY_WME)); + DBUG_RETURN(retval); + } + retval= load_processor.load_old_format_file(net,old_fname,old_len,file); + my_close(file,MYF(MY_WME)); + if (retval != OK_CONTINUE) + DBUG_RETURN(retval); + } + + DBUG_RETURN(OK_CONTINUE); +} + + +static char out_file_name[FN_REFLEN + 1]; + +static Exit_status handle_event_raw_mode(PRINT_EVENT_INFO *print_event_info, + ulong *len, + const char* logname, uint logname_len) +{ + const char *error_msg; + const uchar *read_pos= mysql->net.read_pos + 1; + Log_event_type type; + DBUG_ENTER("handle_event_raw_mode"); + DBUG_ASSERT(opt_raw_mode && remote_opt); + + type= (Log_event_type) read_pos[EVENT_TYPE_OFFSET]; + + if (type == HEARTBEAT_LOG_EVENT) + DBUG_RETURN(OK_CONTINUE); + + if (type == ROTATE_EVENT || type == FORMAT_DESCRIPTION_EVENT) + { + Log_event *ev; + if (!(ev= Log_event::read_log_event(read_pos , + *len - 1, &error_msg, + glob_description_event, + opt_verify_binlog_checksum))) + { + error("Could not construct %s event object: %s", + type == ROTATE_EVENT ? "rotate" : "format description", error_msg); + DBUG_RETURN(ERROR_STOP); + } + /* + If reading from a remote host, ensure the temp_buf for the + Log_event class is pointing to the incoming stream. + */ + ev->register_temp_buf(const_cast(read_pos), FALSE); + + if (type == ROTATE_EVENT) + { + Exit_status ret_val= OK_CONTINUE; + Rotate_log_event *rev= (Rotate_log_event *)ev; + char *pe= strmake(out_file_name, output_prefix, sizeof(out_file_name)-1); + strmake(pe, rev->new_log_ident, sizeof(out_file_name) - (pe-out_file_name)); + + /* + If this is a fake Rotate event, and not about our log, we can stop + transfer. If this a real Rotate event (so it's not about our log, + it's in our log describing the next log), we print it (because it's + part of our log) and then we will stop when we receive the fake one + soon. + */ + if (rev->when == 0) + { + if (!to_last_remote_log) + { + if ((rev->ident_len != logname_len) || + memcmp(rev->new_log_ident, logname, logname_len)) + { + ret_val= OK_EOF; + } + /* + Otherwise, this is a fake Rotate for our log, at the very + beginning for sure. Skip it, because it was not in the original + log. If we are running with to_last_remote_log, we print it, + because it serves as a useful marker between binlogs then. + */ + } + *len= 1; // fake Rotate, so don't increment old_off + ev->temp_buf= 0; + delete ev; + DBUG_RETURN(ret_val); + } + ev->temp_buf= 0; + delete ev; + } + else /* if (type == FORMAT_DESCRIPTION_EVENT) */ + { + DBUG_ASSERT(type == FORMAT_DESCRIPTION_EVENT); + + if (result_file) + my_fclose(result_file, MYF(0)); + + if (!(result_file= my_fopen(out_file_name, + O_WRONLY | O_BINARY, MYF(MY_WME)))) + { + error("Could not create output log file: %s", out_file_name); + DBUG_RETURN(ERROR_STOP); + } + /* TODO - add write error simulation here */ + + if (my_fwrite(result_file, (const uchar *) BINLOG_MAGIC, + BIN_LOG_HEADER_SIZE, MYF(MY_NABP))) + { + error("Could not write into log file '%s'", out_file_name); + DBUG_RETURN(ERROR_STOP); + } + print_event_info->file= result_file; + + delete glob_description_event; + glob_description_event= (Format_description_log_event*) ev; + print_event_info->common_header_len= + glob_description_event->common_header_len; + ev->temp_buf= 0; + /* We do not want to delete the event here. */ + } + } + + if (my_fwrite(result_file, read_pos, *len - 1, MYF(MY_NABP))) + { + error("Could not write into log file '%s'", out_file_name); + DBUG_RETURN(ERROR_STOP); + } + fflush(result_file); + + DBUG_RETURN(OK_CONTINUE); +} + + +/** + Requests binlog dump from a remote server and prints the events it + receives. + + @param[in,out] print_event_info Parameters and context state + determining how to print. + @param[in] logname Name of input binlog. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. + @retval OK_STOP No error, but the end of the specified range of + events to process has been reached and the program should terminate. +*/ +static Exit_status dump_remote_log_entries(PRINT_EVENT_INFO *print_event_info, + const char* logname) + +{ + uchar buf[128]; + ulong len; + uint logname_len; + NET* net; + my_off_t old_off= start_position_mot; + Exit_status retval= OK_CONTINUE; + short binlog_flags = 0; + ulong slave_id; + DBUG_ENTER("dump_remote_log_entries"); + + /* + Even if we already read one binlog (case of >=2 binlogs on command line), + we cannot re-use the same connection as before, because it is now dead + (COM_BINLOG_DUMP kills the thread when it finishes). + */ + if ((retval= safe_connect()) != OK_CONTINUE) + DBUG_RETURN(retval); + net= &mysql->net; + + if ((retval= check_master_version()) != OK_CONTINUE) + DBUG_RETURN(retval); + + /* + COM_BINLOG_DUMP accepts only 4 bytes for the position, so we are forced to + cast to uint32. + */ + DBUG_ASSERT(start_position <= UINT_MAX32); + int4store(buf, (uint32)start_position); + if (!opt_skip_annotate_row_events) + binlog_flags|= BINLOG_SEND_ANNOTATE_ROWS_EVENT; + if (!opt_stop_never) + binlog_flags|= BINLOG_DUMP_NON_BLOCK; + + int2store(buf + BIN_LOG_HEADER_SIZE, binlog_flags); + + size_t tlen = strlen(logname); + if (tlen > sizeof(buf) - 10) + { + error("Log name too long."); + DBUG_RETURN(ERROR_STOP); + } + logname_len = (uint) tlen; + if (opt_stop_never) + { + DBUG_ASSERT(to_last_remote_log); + slave_id= (opt_stop_never_slave_server_id == 0) ? + 1 : opt_stop_never_slave_server_id; + } + else + slave_id= 0; + int4store(buf + 6, slave_id); + memcpy(buf + 10, logname, logname_len); + if (simple_command(mysql, COM_BINLOG_DUMP, buf, logname_len + 10, 1)) + { + error("Got fatal error sending the log dump command."); + DBUG_RETURN(ERROR_STOP); + } + + for (;;) + { + len= cli_safe_read(mysql); + if (len == packet_error) + { + error("Got error reading packet from server: %s", mysql_error(mysql)); + DBUG_RETURN(ERROR_STOP); + } + if (len < 8 && net->read_pos[0] == 254) + break; // end of data + DBUG_PRINT("info",( "len: %lu net->read_pos[5]: %d\n", + len, net->read_pos[5])); + if (opt_raw_mode) + { + retval= handle_event_raw_mode(print_event_info, &len, + logname, logname_len); + } + else + { + retval= handle_event_text_mode(print_event_info, &len, + logname, logname_len, old_off); + } + if (retval != OK_CONTINUE) + { + if (retval == OK_EOF) + break; + DBUG_RETURN(retval); + } + + /* + Let's adjust offset for remote log as for local log to produce + similar text and to have --stop-position to work identically. + */ + old_off+= len-1; + } + + DBUG_RETURN(OK_CONTINUE); +} + + +/** + Reads the @c Format_description_log_event from the beginning of a + local input file. + + The @c Format_description_log_event is only read if it is outside + the range specified with @c --start-position; otherwise, it will be + seen later. If this is an old binlog, a fake @c + Format_description_event is created. This also prints a @c + Format_description_log_event to the output, unless we reach the + --start-position range. In this case, it is assumed that a @c + Format_description_log_event will be found when reading events the + usual way. + + @param file The file to which a @c Format_description_log_event will + be printed. + + @param[in,out] print_event_info Parameters and context state + determining how to print. + + @param[in] logname Name of input binlog. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. + @retval OK_STOP No error, but the end of the specified range of + events to process has been reached and the program should terminate. +*/ +static Exit_status check_header(IO_CACHE* file, + PRINT_EVENT_INFO *print_event_info, + const char* logname) +{ + uchar header[BIN_LOG_HEADER_SIZE]; + uchar buf[PROBE_HEADER_LEN]; + my_off_t tmp_pos, pos; + MY_STAT my_file_stat; + + delete glob_description_event; + if (!(glob_description_event= new Format_description_log_event(3))) + { + error("Failed creating Format_description_log_event; out of memory?"); + return ERROR_STOP; + } + + pos= my_b_tell(file); + + /* fstat the file to check if the file is a regular file. */ + if (my_fstat(file->file, &my_file_stat, MYF(0)) == -1) + { + error("Unable to stat the file."); + return ERROR_STOP; + } + if ((my_file_stat.st_mode & S_IFMT) == S_IFREG) + my_b_seek(file, (my_off_t)0); + + if (my_b_read(file, header, sizeof(header))) + { + error("Failed reading header; probably an empty file."); + return ERROR_STOP; + } + if (memcmp(header, BINLOG_MAGIC, sizeof(header))) + { + error("File is not a binary log file."); + return ERROR_STOP; + } + + /* + Imagine we are running with --start-position=1000. We still need + to know the binlog format's. So we still need to find, if there is + one, the Format_desc event, or to know if this is a 3.23 + binlog. So we need to first read the first events of the log, + those around offset 4. Even if we are reading a 3.23 binlog from + the start (no --start-position): we need to know the header length + (which is 13 in 3.23, 19 in 4.x) to be able to successfully print + the first event (Start_log_event_v3). So even in this case, we + need to "probe" the first bytes of the log *before* we do a real + read_log_event(). Because read_log_event() needs to know the + header's length to work fine. + */ + for(;;) + { + tmp_pos= my_b_tell(file); /* should be 4 the first time */ + if (my_b_read(file, buf, sizeof(buf))) + { + if (file->error) + { + error("Could not read entry at offset %llu: " + "Error in log format or read error.", (ulonglong)tmp_pos); + return ERROR_STOP; + } + /* + Otherwise this is just EOF : this log currently contains 0-2 + events. Maybe it's going to be filled in the next + milliseconds; then we are going to have a problem if this a + 3.23 log (imagine we are locally reading a 3.23 binlog which + is being written presently): we won't know it in + read_log_event() and will fail(). Similar problems could + happen with hot relay logs if --start-position is used (but a + --start-position which is posterior to the current size of the log). + These are rare problems anyway (reading a hot log + when we + read the first events there are not all there yet + when we + read a bit later there are more events + using a strange + --start-position). + */ + break; + } + else + { + DBUG_PRINT("info",("buf[EVENT_TYPE_OFFSET=%d]=%d", + EVENT_TYPE_OFFSET, buf[EVENT_TYPE_OFFSET])); + /* always test for a Start_v3, even if no --start-position */ + if (buf[EVENT_TYPE_OFFSET] == START_EVENT_V3) + { + /* This is 3.23 or 4.x */ + if (uint4korr(buf + EVENT_LEN_OFFSET) < + (LOG_EVENT_MINIMAL_HEADER_LEN + START_V3_HEADER_LEN)) + { + /* This is 3.23 (format 1) */ + delete glob_description_event; + if (!(glob_description_event= new Format_description_log_event(1))) + { + error("Failed creating Format_description_log_event; " + "out of memory?"); + return ERROR_STOP; + } + } + break; + } + else if (tmp_pos >= start_position) + break; + else if (buf[EVENT_TYPE_OFFSET] == FORMAT_DESCRIPTION_EVENT) + { + /* This is 5.0 */ + Format_description_log_event *new_description_event; + my_b_seek(file, tmp_pos); /* seek back to event's start */ + if (!(new_description_event= (Format_description_log_event*) + Log_event::read_log_event(file, glob_description_event, + opt_verify_binlog_checksum))) + /* EOF can't be hit here normally, so it's a real error */ + { + error("Could not read a Format_description_log_event event at " + "offset %llu; this could be a log format error or read error.", + (ulonglong)tmp_pos); + return ERROR_STOP; + } + if (opt_base64_output_mode == BASE64_OUTPUT_AUTO) + { + /* + process_event will delete *description_event and set it to + the new one, so we should not do it ourselves in this + case. + */ + Exit_status retval= process_event(print_event_info, + new_description_event, tmp_pos, + logname); + if (retval != OK_CONTINUE) + return retval; + } + else + { + delete glob_description_event; + glob_description_event= new_description_event; + } + DBUG_PRINT("info",("Setting description_event")); + } + else if (buf[EVENT_TYPE_OFFSET] == ROTATE_EVENT) + { + Log_event *ev; + my_b_seek(file, tmp_pos); /* seek back to event's start */ + if (!(ev= Log_event::read_log_event(file, glob_description_event, + opt_verify_binlog_checksum))) + { + /* EOF can't be hit here normally, so it's a real error */ + error("Could not read a Rotate_log_event event at offset %llu;" + " this could be a log format error or read error.", + (ulonglong)tmp_pos); + return ERROR_STOP; + } + delete ev; + } + else + break; + } + } + my_b_seek(file, pos); + return OK_CONTINUE; +} + + +/** + Reads a local binlog and prints the events it sees. + + @param[in] logname Name of input binlog. + + @param[in,out] print_event_info Parameters and context state + determining how to print. + + @retval ERROR_STOP An error occurred - the program should terminate. + @retval OK_CONTINUE No error, the program should continue. + @retval OK_STOP No error, but the end of the specified range of + events to process has been reached and the program should terminate. +*/ +static Exit_status dump_local_log_entries(PRINT_EVENT_INFO *print_event_info, + const char* logname) +{ + File fd = -1; + IO_CACHE cache,*file= &cache; + uchar tmp_buff[BIN_LOG_HEADER_SIZE]; + Exit_status retval= OK_CONTINUE; + + if (logname && strcmp(logname, "-") != 0) + { + /* read from normal file */ + if ((fd = my_open(logname, O_RDONLY | O_BINARY, MYF(MY_WME))) < 0) + return ERROR_STOP; + if (init_io_cache(file, fd, 0, READ_CACHE, start_position_mot, 0, + MYF(MY_WME | MY_NABP))) + { + my_close(fd, MYF(MY_WME)); + return ERROR_STOP; + } + if ((retval= check_header(file, print_event_info, logname)) != OK_CONTINUE) + goto end; + } + else + { + /* read from stdin */ + /* + Windows opens stdin in text mode by default. Certain characters + such as CTRL-Z are interpreted as events and the read() method + will stop. CTRL-Z is the EOF marker in Windows. to get past this + you have to open stdin in binary mode. Setmode() is used to set + stdin in binary mode. Errors on setting this mode result in + halting the function and printing an error message to stderr. + */ +#if defined (_WIN32) + if (_setmode(fileno(stdin), O_BINARY) == -1) + { + error("Could not set binary mode on stdin."); + return ERROR_STOP; + } +#endif + if (init_io_cache(file, my_fileno(stdin), 0, READ_CACHE, (my_off_t) 0, + 0, MYF(MY_WME | MY_NABP | MY_DONT_CHECK_FILESIZE))) + { + error("Failed to init IO cache."); + return ERROR_STOP; + } + if ((retval= check_header(file, print_event_info, logname)) != OK_CONTINUE) + goto end; + if (start_position) + { + /* skip 'start_position' characters from stdin */ + uchar buff[IO_SIZE]; + my_off_t length,tmp; + for (length= start_position_mot ; length > 0 ; length-=tmp) + { + tmp= MY_MIN(length,sizeof(buff)); + if (my_b_read(file, buff, (uint) tmp)) + { + error("Failed reading from file."); + goto err; + } + } + } + } + + if (!glob_description_event || !glob_description_event->is_valid()) + { + error("Invalid Format_description log event; could be out of memory."); + goto err; + } + + if (!start_position && my_b_read(file, tmp_buff, BIN_LOG_HEADER_SIZE)) + { + error("Failed reading from file."); + goto err; + } + for (;;) + { + char llbuff[21]; + my_off_t old_off = my_b_tell(file); + + Log_event* ev = Log_event::read_log_event(file, glob_description_event, + opt_verify_binlog_checksum); + if (!ev) + { + /* + if binlog wasn't closed properly ("in use" flag is set) don't complain + about a corruption, but treat it as EOF and move to the next binlog. + */ + if (glob_description_event->flags & LOG_EVENT_BINLOG_IN_USE_F) + file->error= 0; + else if (file->error) + { + error("Could not read entry at offset %s: " + "Error in log format or read error.", + llstr(old_off,llbuff)); + goto err; + } + // file->error == 0 means EOF, that's OK, we break in this case + goto end; + } + if ((retval= process_event(print_event_info, ev, old_off, logname)) != + OK_CONTINUE) + goto end; + } + + /* NOTREACHED */ + +err: + retval= ERROR_STOP; + +end: + if (fd >= 0) + my_close(fd, MYF(MY_WME)); + /* + Since the end_io_cache() writes to the + file errors may happen. + */ + if (end_io_cache(file)) + retval= ERROR_STOP; + + return retval; +} + + +int main(int argc, char** argv) +{ + Exit_status retval= OK_CONTINUE; + ulonglong save_stop_position; + MY_INIT(argv[0]); + DBUG_ENTER("main"); + DBUG_PROCESS(argv[0]); + + my_init_time(); // for time functions + tzset(); // set tzname + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_groups, &argc, &argv); + defaults_argv= argv; + + init_alloc_root(PSI_NOT_INSTRUMENTED, &glob_root, 1024, 0, MYF(0)); + + if (!(binlog_filter= new Rpl_filter)) + { + error("Failed to create Rpl_filter"); + goto err; + } + + parse_args(&argc, (char***)&argv); + + if (!argc || opt_version) + { + if (!opt_version) + { + usage(); + retval= ERROR_STOP; + } + goto err; + } + + if (opt_base64_output_mode == BASE64_OUTPUT_UNSPEC) + opt_base64_output_mode= BASE64_OUTPUT_AUTO; + + my_set_max_open_files(open_files_limit); + + if (opt_flashback && opt_raw_mode) + { + error("The --raw mode is not allowed with --flashback mode"); + die(1); + } + + if (opt_flashback) + { + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &binlog_events, + sizeof(LEX_STRING), 1024, 1024, MYF(0)); + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &events_in_stmt, + sizeof(Rows_log_event*), 1024, 1024, MYF(0)); + } + if (opt_stop_never) + to_last_remote_log= TRUE; + + if (opt_raw_mode) + { + if (!remote_opt) + { + error("The --raw mode only works with --read-from-remote-server"); + die(1); + } + if (one_database) + warning("The --database option is ignored in raw mode"); + + if (stop_position != (ulonglong)(~(my_off_t)0)) + warning("The --stop-position option is ignored in raw mode"); + + if (stop_datetime != MY_TIME_T_MAX) + warning("The --stop-datetime option is ignored in raw mode"); + result_file= 0; + if (result_file_name) + output_prefix= result_file_name; + } + else + { + if (result_file_name) + { + if (!(result_file= my_fopen(result_file_name, + O_WRONLY | O_BINARY, MYF(MY_WME)))) + { + error("Could not create log file '%s'", result_file_name); + die(1); + } + } + else + result_file= stdout; + } + + MY_TMPDIR tmpdir; + tmpdir.list= 0; + if (!dirname_for_local_load) + { + if (init_tmpdir(&tmpdir, 0)) + { + retval= ERROR_STOP; + goto err; + } + dirname_for_local_load= my_strdup(PSI_NOT_INSTRUMENTED, my_tmpdir(&tmpdir), MY_WME); + } + + if (load_processor.init()) + { + retval= ERROR_STOP; + goto err; + } + if (dirname_for_local_load) + load_processor.init_by_dir_name(dirname_for_local_load); + else + load_processor.init_by_cur_dir(); + + if (!opt_raw_mode) + { + fprintf(result_file, "/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;\n"); + + fprintf(result_file, + "/*!40019 SET @@session.max_delayed_threads=0*/;\n"); + + if (disable_log_bin) + fprintf(result_file, + "/*!32316 SET @OLD_SQL_LOG_BIN=@@SQL_LOG_BIN, SQL_LOG_BIN=0*/;\n"); + + /* + In mysqlbinlog|mysql, don't want mysql to be disconnected after each + transaction (which would be the case with GLOBAL.COMPLETION_TYPE==2). + */ + fprintf(result_file, + "/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE," + "COMPLETION_TYPE=0*/;\n"); + + if (charset) + fprintf(result_file, + "\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" + "\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" + "\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" + "\n/*!40101 SET NAMES %s */;\n", charset); + } + + for (save_stop_position= stop_position, stop_position= ~(my_off_t)0 ; + (--argc >= 0) ; ) + { + if (argc == 0) // last log, --stop-position applies + stop_position= save_stop_position; + if ((retval= dump_log_entries(*argv++)) != OK_CONTINUE) + break; + + // For next log, --start-position does not apply + start_position= BIN_LOG_HEADER_SIZE; + } + + /* + If enable flashback, need to print the events from the end to the + beginning + */ + if (opt_flashback && retval != ERROR_STOP) + { + for (size_t i= binlog_events.elements; i > 0; --i) + { + LEX_STRING *event_str= dynamic_element(&binlog_events, i - 1, + LEX_STRING*); + fprintf(result_file, "%s", event_str->str); + my_free(event_str->str); + } + fprintf(result_file, "COMMIT\n/*!*/;\n"); + delete_dynamic(&binlog_events); + delete_dynamic(&events_in_stmt); + } + + /* Set delimiter back to semicolon */ + if (retval != ERROR_STOP) + { + if (!stop_event_string.is_empty() && result_file) + fprintf(result_file, "%s", stop_event_string.ptr()); + if (!opt_raw_mode && opt_flashback) + fprintf(result_file, "DELIMITER ;\n"); + } + + if (retval != ERROR_STOP && !opt_raw_mode) + { + /* + Issue a ROLLBACK in case the last printed binlog was crashed and had half + of transaction. + */ + fprintf(result_file, + "# End of log file\nROLLBACK /* added by mysqlbinlog */;\n" + "/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;\n"); + if (disable_log_bin) + fprintf(result_file, "/*!32316 SET SQL_LOG_BIN=@OLD_SQL_LOG_BIN*/;\n"); + + if (charset) + fprintf(result_file, + "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n" + "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" + "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"); + fprintf(result_file, "/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;\n"); + + if (gtid_event_filter) + { + fprintf(result_file, + "/*!100001 SET @@SESSION.SERVER_ID=@@GLOBAL.SERVER_ID */;\n" + "/*!100001 SET @@SESSION.GTID_DOMAIN_ID=@@GLOBAL.GTID_DOMAIN_ID " + "*/;\n"); + } + } + + if (tmpdir.list) + free_tmpdir(&tmpdir); + if (result_file) + { + if (result_file != stdout) + my_fclose(result_file, MYF(0)); + else + fflush(result_file); + } + + /* + Ensure the GTID state is correct. If not, end in error. + + Note that in gtid strict mode, we will not report here if any invalid GTIDs + are processed because it immediately errors (i.e. retval will be + ERROR_STOP) + */ + if (retval != ERROR_STOP && gtid_state_validator && + gtid_state_validator->report(stderr, opt_gtid_strict_mode)) + retval= ERROR_STOP; + + cleanup(); + /* We cannot free DBUG, it is used in global destructors after exit(). */ + my_end(my_end_arg | MY_DONT_FREE_DBUG); + + exit(retval == ERROR_STOP ? 1 : 0); + /* Keep compilers happy. */ + DBUG_RETURN(retval == ERROR_STOP ? 1 : 0); + +err: + cleanup(); + my_end(my_end_arg); + exit(retval == ERROR_STOP ? 1 : 0); + DBUG_RETURN(retval == ERROR_STOP ? 1 : 0); +} + +uint e_key_get_latest_version_func(uint) { return 1; } +uint e_key_get_func(uint, uint, uchar*, uint*) { return 1; } +uint e_ctx_size_func(uint, uint) { return 1; } +int e_ctx_init_func(void *, const uchar*, uint, const uchar*, uint, + int, uint, uint) { return 1; } +int e_ctx_update_func(void *, const uchar*, uint, uchar*, uint*) { return 1; } +int e_ctx_finish_func(void *, uchar*, uint*) { return 1; } +uint e_encrypted_length_func(uint, uint, uint) { return 1; } + +struct encryption_service_st encryption_handler= +{ + e_key_get_latest_version_func, + e_key_get_func, + e_ctx_size_func, + e_ctx_init_func, + e_ctx_update_func, + e_ctx_finish_func, + e_encrypted_length_func +}; + +/* + We must include this here as it's compiled with different options for + the server +*/ + +#include "rpl_tblmap.cc" +#undef TABLE +#include "my_decimal.h" +#include "decimal.c" +#include "my_decimal.cc" +#include "../sql-common/my_time.c" +#include "password.c" +#include "log_event.cc" +#include "log_event_client.cc" +#include "log_event_old.cc" +#include "rpl_utility.cc" +#include "sql_string.cc" +#include "sql_list.cc" +#include "rpl_filter.cc" +#include "compat56.cc" +#include "rpl_gtid.cc" diff --git a/client/mysqlcheck.c b/client/mysqlcheck.c new file mode 100644 index 00000000..2f366ec0 --- /dev/null +++ b/client/mysqlcheck.c @@ -0,0 +1,1298 @@ +/* + Copyright (c) 2001, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2012, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* By Jani Tolonen, 2001-04-20, MySQL Development Team */ + +#define CHECK_VERSION "2.7.4-MariaDB" + +#include "client_priv.h" +#include +#include +#include +#include +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +/* Exit codes */ + +#define EX_USAGE 1 +#define EX_MYSQLERR 2 + +/* ALTER instead of repair. */ +#define MAX_ALTER_STR_SIZE 128 * 1024 +#define KEY_PARTITIONING_CHANGED_STR "KEY () partitioning changed" + +static MYSQL mysql_connection, *sock = 0; +static my_bool opt_alldbs = 0, opt_check_only_changed = 0, opt_extended = 0, + opt_compress = 0, opt_databases = 0, opt_fast = 0, + opt_medium_check = 0, opt_quick = 0, opt_all_in_1 = 0, + opt_silent = 0, opt_auto_repair = 0, ignore_errors = 0, + tty_password= 0, opt_frm= 0, debug_info_flag= 0, debug_check_flag= 0, + opt_fix_table_names= 0, opt_fix_db_names= 0, opt_upgrade= 0, + opt_persistent_all= 0, opt_do_tables= 1; +static my_bool opt_write_binlog= 1, opt_flush_tables= 0; +static uint verbose = 0, opt_mysql_port=0; +static int my_end_arg; +static char * opt_mysql_unix_port = 0; +static char *opt_password = 0, *current_user = 0, + *default_charset= 0, *current_host= 0; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; +static int first_error = 0; +static char *opt_skip_database; +DYNAMIC_ARRAY tables4repair, tables4rebuild, alter_table_cmds; +DYNAMIC_ARRAY views4repair; +static uint opt_protocol=0; + +enum operations { DO_CHECK=1, DO_REPAIR, DO_ANALYZE, DO_OPTIMIZE, DO_FIX_NAMES }; +const char *operation_name[]= +{ + "???", "check", "repair", "analyze", "optimize", "fix names" +}; + +typedef enum { DO_VIEWS_NO, DO_VIEWS_YES, DO_UPGRADE, DO_VIEWS_FROM_MYSQL } enum_do_views; +const char *do_views_opts[]= {"NO", "YES", "UPGRADE", "UPGRADE_FROM_MYSQL", + NullS}; +TYPELIB do_views_typelib= { array_elements(do_views_opts) - 1, "", + do_views_opts, NULL }; +static ulong opt_do_views= DO_VIEWS_NO; + +static struct my_option my_long_options[] = +{ + {"all-databases", 'A', + "Check all the databases. This is the same as --databases with all databases selected.", + &opt_alldbs, &opt_alldbs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"analyze", 'a', "Analyze given tables.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"all-in-1", '1', + "Instead of issuing one query for each table, use one query per database, naming all tables in the database in a comma-separated list.", + &opt_all_in_1, &opt_all_in_1, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"auto-repair", OPT_AUTO_REPAIR, + "If a checked table is corrupted, automatically fix it. Repairing will be done after all tables have been checked, if corrupted ones were found.", + &opt_auto_repair, &opt_auto_repair, 0, GET_BOOL, NO_ARG, 0, + 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", (char**) &charsets_dir, + (char**) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"check", 'c', "Check table for errors.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"check-only-changed", 'C', + "Check only tables that have changed since last check or haven't been closed properly.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"check-upgrade", 'g', + "Check tables for version-dependent changes. May be used with --auto-repair to correct tables requiring version-dependent updates.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", OPT_COMPRESS, "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"databases", 'B', + "Check several databases. Note the difference in usage; in this case no tables are given. All name arguments are regarded as database names.", + &opt_databases, &opt_databases, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"fast",'F', "Check only tables that haven't been closed properly.", + &opt_fast, &opt_fast, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"fix-db-names", OPT_FIX_DB_NAMES, "Fix database names.", + &opt_fix_db_names, &opt_fix_db_names, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"fix-table-names", OPT_FIX_TABLE_NAMES, "Fix table names.", + &opt_fix_table_names, &opt_fix_table_names, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"force", 'f', "Continue even if we get an SQL error.", + &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"extended", 'e', + "If you are using this option with CHECK TABLE, it will ensure that the table is 100 percent consistent, but will take a long time. If you are using this option with REPAIR TABLE, it will force using old slow repair with keycache method, instead of much faster repair by sorting.", + &opt_extended, &opt_extended, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"flush", OPT_FLUSH_TABLES, "Flush each table after check. This is useful if you don't want to have the checked tables take up space in the caches after the check", + &opt_flush_tables, &opt_flush_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0 }, + {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host",'h', "Connect to host.", ¤t_host, + ¤t_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"medium-check", 'm', + "Faster than extended-check, but only finds 99.99 percent of all errors. Should be good enough for most cases.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"write-binlog", OPT_WRITE_BINLOG, + "Log ANALYZE, OPTIMIZE and REPAIR TABLE commands. Use --skip-write-binlog " + "when commands should not be sent to replication slaves.", + &opt_write_binlog, &opt_write_binlog, 0, GET_BOOL, NO_ARG, + 1, 0, 0, 0, 0, 0}, + {"optimize", 'o', "Optimize table.", 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given, it's solicited on the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"persistent", 'Z', + "When using ANALYZE TABLE use the PERSISTENT FOR ALL option.", + &opt_persistent_all, &opt_persistent_all, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"quick", 'q', + "If you are using this option with CHECK TABLE, it prevents the check from scanning the rows to check for wrong links. This is the fastest check. If you are using this option with REPAIR TABLE, it will try to repair only the index tree. This is the fastest repair method for a table.", + &opt_quick, &opt_quick, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"repair", 'r', + "Can fix almost anything except unique keys that aren't unique.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Print only error messages.", &opt_silent, + &opt_silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"skip_database", 0, "Don't process the database specified as argument", + &opt_skip_database, &opt_skip_database, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include + {"tables", OPT_TABLES, "Overrides option --databases (-B).", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"use-frm", OPT_FRM, + "When used with REPAIR, get table structure from .frm file, so the table can be repaired even if .MYI header is corrupted.", + &opt_frm, &opt_frm, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", ¤t_user, + ¤t_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', "Print info about the various stages; Using it 3 times will print out all CHECK, RENAME and ALTER TABLE during the check phase.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"process-views", 0, + "Perform the requested operation (check or repair) on views. " + "One of: NO, YES (correct the checksum, if necessary, add the " + "mariadb-version field), UPGRADE (run from mariadb-upgrade), " + "UPGRADE_FROM_MYSQL (same as YES and toggle the algorithm " + "MERGE<->TEMPTABLE.", &opt_do_views, &opt_do_views, + &do_views_typelib, GET_ENUM, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"process-tables", 0, "Perform the requested operation on tables.", + &opt_do_tables, &opt_do_tables, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +static const char *load_default_groups[]= +{ "mysqlcheck", "mariadb-check", "client", "client-server", "client-mariadb", + 0 }; + + +static void print_version(void); +static void usage(void); +static int get_options(int *argc, char ***argv); +static int process_all_databases(); +static int process_databases(char **db_names); +static int process_selected_tables(char *db, char **table_names, int tables); +static int process_all_tables_in_db(char *database); +static int process_one_db(char *database); +static int use_db(char *database); +static int handle_request_for_tables(char *, size_t, my_bool, my_bool); +static int dbConnect(char *host, char *user,char *passwd); +static void dbDisconnect(char *host); +static void DBerror(MYSQL *mysql, const char *when); +static void safe_exit(int error); +static void print_result(); +static size_t fixed_name_length(const char *name); +static char *fix_table_name(char *dest, char *src); +int what_to_do = 0; + + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n", my_progname, CHECK_VERSION, + MYSQL_SERVER_VERSION, SYSTEM_TYPE, MACHINE_TYPE); +} /* print_version */ + + +static void usage(void) +{ + DBUG_ENTER("usage"); + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + puts("This program can be used to CHECK (-c, -m, -C), REPAIR (-r), ANALYZE (-a),"); + puts("or OPTIMIZE (-o) tables. Some of the options (like -e or -q) can be"); + puts("used at the same time. Not all options are supported by all storage engines."); + puts("The options -c, -r, -a, and -o are exclusive to each other, which"); + puts("means that the last option will be used, if several was specified.\n"); + puts("The option -c (--check) will be used by default, if none was specified."); + puts("You can change the default behavior by making a symbolic link, or"); + puts("copying this file somewhere with another name, the alternatives are:"); + puts("mysqlrepair: The default option will be -r"); + puts("mysqlanalyze: The default option will be -a"); + puts("mysqloptimize: The default option will be -o\n"); + printf("Usage: %s [OPTIONS] database [tables]\n", my_progname); + printf("OR %s [OPTIONS] --databases DB1 [DB2 DB3...]\n", + my_progname); + puts("Please consult the MariaDB Knowledge Base at"); + puts("https://mariadb.com/kb/en/mysqlcheck for latest information about"); + puts("this program."); + print_defaults("my", load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); + DBUG_VOID_RETURN; +} /* usage */ + + +static my_bool +get_one_option(const struct my_option *opt, + const char *argument, + const char *filename) +{ + int orig_what_to_do= what_to_do; + DBUG_ENTER("get_one_option"); + + switch(opt->id) { + case 'a': + what_to_do = DO_ANALYZE; + break; + case 'c': + what_to_do = DO_CHECK; + break; + case 'C': + what_to_do = DO_CHECK; + opt_check_only_changed = 1; + break; + case 'I': /* Fall through */ + case '?': + usage(); + exit(0); + case 'm': + what_to_do = DO_CHECK; + opt_medium_check = 1; + break; + case 'o': + what_to_do = DO_OPTIMIZE; + break; + case OPT_FIX_DB_NAMES: + what_to_do= DO_FIX_NAMES; + opt_databases= 1; + break; + case OPT_FIX_TABLE_NAMES: + what_to_do= DO_FIX_NAMES; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password = my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1] = 0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password = 1; + break; + case 'r': + what_to_do = DO_REPAIR; + break; + case 'g': + what_to_do= DO_CHECK; + opt_upgrade= 1; + break; + case 'W': +#ifdef _WIN32 + opt_protocol = MYSQL_PROTOCOL_PIPE; +#endif + break; + case '#': + DBUG_PUSH(argument ? argument : "d:t:o"); + debug_check_flag= 1; + break; +#include + case OPT_TABLES: + opt_databases = 0; + break; + case 'v': + verbose++; + break; + case 'V': + print_version(); exit(0); + break; + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + } + + if (orig_what_to_do && (what_to_do != orig_what_to_do)) + { + fprintf(stderr, "Error: %s doesn't support multiple contradicting commands.\n", + my_progname); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} + + +static int get_options(int *argc, char ***argv) +{ + int ho_error; + DBUG_ENTER("get_options"); + + if (*argc == 1) + { + usage(); + exit(0); + } + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (what_to_do == DO_REPAIR && !opt_do_views && !opt_do_tables) + { + fprintf(stderr, "Error: Nothing to repair when both " + "--process-tables=NO and --process-views=NO\n"); + exit(1); + } + if (!what_to_do) + { + size_t pnlen= strlen(my_progname); + + if (pnlen < 6) /* name too short */ + what_to_do = DO_CHECK; + else if (!strcmp("repair", my_progname + pnlen - 6)) + what_to_do = DO_REPAIR; + else if (!strcmp("analyze", my_progname + pnlen - 7)) + what_to_do = DO_ANALYZE; + else if (!strcmp("optimize", my_progname + pnlen - 8)) + what_to_do = DO_OPTIMIZE; + else + what_to_do = DO_CHECK; + } + + if (opt_do_views && what_to_do != DO_REPAIR && what_to_do != DO_CHECK) + { + fprintf(stderr, "Error: %s doesn't support %s for views.\n", + my_progname, operation_name[what_to_do]); + exit(1); + } + + /* + If there's no --default-character-set option given with + --fix-table-name or --fix-db-name set the default character set to "utf8". + */ + if (!default_charset) + { + if (opt_fix_db_names || opt_fix_table_names) + default_charset= (char*) "utf8"; + else + default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME; + } + if (!strcmp(default_charset, MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + + if (!get_charset_by_csname(default_charset, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME))) + { + printf("Unsupported character set: %s\n", default_charset); + DBUG_RETURN(1); + } + my_set_console_cp(default_charset); + if (*argc > 0 && opt_alldbs) + { + printf("You should give only options, no arguments at all, with option\n"); + printf("--all-databases. Please see %s --help for more information.\n", + my_progname); + DBUG_RETURN(1); + } + if (*argc < 1 && !opt_alldbs) + { + printf("You forgot to give the arguments! Please see %s --help\n", + my_progname); + printf("for more information.\n"); + DBUG_RETURN(1); + } + if (tty_password) + opt_password = my_get_tty_password(NullS); + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + DBUG_RETURN((0)); +} /* get_options */ + + +static int process_all_databases() +{ + MYSQL_ROW row; + MYSQL_RES *tableres; + int result = 0; + DBUG_ENTER("process_all_databases"); + + if (mysql_query(sock, "SHOW DATABASES") || + !(tableres = mysql_store_result(sock))) + { + my_printf_error(0, "Error: Couldn't execute 'SHOW DATABASES': %s", + MYF(0), mysql_error(sock)); + DBUG_RETURN(1); + } + if (verbose) + printf("Processing databases\n"); + while ((row = mysql_fetch_row(tableres))) + { + if (process_one_db(row[0])) + result = 1; + } + mysql_free_result(tableres); + DBUG_RETURN(result); +} +/* process_all_databases */ + + +static int process_databases(char **db_names) +{ + int result = 0; + DBUG_ENTER("process_databases"); + + if (verbose) + printf("Processing databases\n"); + for ( ; *db_names ; db_names++) + { + if (process_one_db(*db_names)) + result = 1; + } + DBUG_RETURN(result); +} /* process_databases */ + + +/* returns: -1 for error, 1 for view, 0 for table */ +static int is_view(const char *table) +{ + char query[1024]; + MYSQL_RES *res; + MYSQL_FIELD *field; + int view; + DBUG_ENTER("is_view"); + + my_snprintf(query, sizeof(query), "SHOW CREATE TABLE %`s", table); + if (mysql_query(sock, query)) + { + fprintf(stderr, "Failed to %s\n", query); + fprintf(stderr, "Error: %s\n", mysql_error(sock)); + DBUG_RETURN(-1); + } + res= mysql_store_result(sock); + field= mysql_fetch_field(res); + view= (strcmp(field->name,"View") == 0) ? 1 : 0; + mysql_free_result(res); + + DBUG_RETURN(view); +} + +static int process_selected_tables(char *db, char **table_names, int tables) +{ + int view; + char *table; + size_t table_len; + DBUG_ENTER("process_selected_tables"); + + if (use_db(db)) + DBUG_RETURN(1); + if (opt_all_in_1 && what_to_do != DO_FIX_NAMES) + { + /* + We need table list in form `a`, `b`, `c` + that's why we need 2 more chars added to to each table name + space is for more readable output in logs and in case of error + */ + char *table_names_comma_sep, *end; + size_t tot_length= 0; + int i= 0; + + if (opt_do_tables && opt_do_views) + { + fprintf(stderr, "Error: %s cannot process both tables and views " + "in one command (--process-tables=YES " + "--process-views=YES --all-in-1).\n", + my_progname); + DBUG_RETURN(1); + } + + for (i = 0; i < tables; i++) + tot_length+= fixed_name_length(*(table_names + i)) + 2; + + if (!(table_names_comma_sep = (char *) + my_malloc(PSI_NOT_INSTRUMENTED, tot_length + 4, MYF(MY_WME)))) + DBUG_RETURN(1); + + for (end = table_names_comma_sep + 1; tables > 0; + tables--, table_names++) + { + end= fix_table_name(end, *table_names); + *end++= ','; + } + *--end = 0; + handle_request_for_tables(table_names_comma_sep + 1, tot_length - 1, + opt_do_views != 0, opt_all_in_1); + my_free(table_names_comma_sep); + } + else + { + for (; tables > 0; tables--, table_names++) + { + table= *table_names; + table_len= fixed_name_length(*table_names); + view= is_view(table); + if (view < 0) + continue; + handle_request_for_tables(table, table_len, view == 1, opt_all_in_1); + } + } + DBUG_RETURN(0); +} /* process_selected_tables */ + + +static size_t fixed_name_length(const char *name) +{ + const char *p; + size_t extra_length= 2; /* count the first/last backticks */ + DBUG_ENTER("fixed_name_length"); + + for (p= name; *p; p++) + { + if (*p == '`') + extra_length++; + } + DBUG_RETURN((size_t) ((p - name) + extra_length)); +} + + +static char *fix_table_name(char *dest, char *src) +{ + DBUG_ENTER("fix_table_name"); + + *dest++= '`'; + for (; *src; src++) + { + if (*src == '`') + *dest++= '`'; + *dest++= *src; + } + *dest++= '`'; + + DBUG_RETURN(dest); +} + + +static int process_all_tables_in_db(char *database) +{ + MYSQL_RES *UNINIT_VAR(res); + MYSQL_ROW row; + uint num_columns; + my_bool system_database= 0; + my_bool view= FALSE; + DBUG_ENTER("process_all_tables_in_db"); + + if (use_db(database)) + DBUG_RETURN(1); + if ((mysql_query(sock, "SHOW /*!50002 FULL*/ TABLES") && + mysql_query(sock, "SHOW TABLES")) || + !(res= mysql_store_result(sock))) + { + my_printf_error(0, "Error: Couldn't get table list for database %s: %s", + MYF(0), database, mysql_error(sock)); + DBUG_RETURN(1); + } + + if (!strcmp(database, "mysql") || !strcmp(database, "MYSQL")) + system_database= 1; + + num_columns= mysql_num_fields(res); + + if (opt_all_in_1 && what_to_do != DO_FIX_NAMES) + { + /* + We need table list in form `a`, `b`, `c` + that's why we need 2 more chars added to to each table name + space is for more readable output in logs and in case of error + */ + + char *tables, *end; + size_t tot_length = 0; + + char *views, *views_end; + size_t tot_views_length = 0; + + while ((row = mysql_fetch_row(res))) + { + if ((num_columns == 2) && (strcmp(row[1], "VIEW") == 0) && + opt_do_views) + tot_views_length+= fixed_name_length(row[0]) + 2; + else if (opt_do_tables) + tot_length+= fixed_name_length(row[0]) + 2; + } + mysql_data_seek(res, 0); + + if (!(tables=(char *) my_malloc(PSI_NOT_INSTRUMENTED, tot_length+4, MYF(MY_WME)))) + { + mysql_free_result(res); + DBUG_RETURN(1); + } + if (!(views=(char *) my_malloc(PSI_NOT_INSTRUMENTED, tot_views_length+4, MYF(MY_WME)))) + { + my_free(tables); + mysql_free_result(res); + DBUG_RETURN(1); + } + + for (end = tables + 1, views_end= views + 1; (row = mysql_fetch_row(res)) ;) + { + if ((num_columns == 2) && (strcmp(row[1], "VIEW") == 0)) + { + if (!opt_do_views) + continue; + views_end= fix_table_name(views_end, row[0]); + *views_end++= ','; + } + else + { + if (!opt_do_tables) + continue; + end= fix_table_name(end, row[0]); + *end++= ','; + } + } + *--end = 0; + *--views_end = 0; + if (tot_length) + handle_request_for_tables(tables + 1, tot_length - 1, FALSE, opt_all_in_1); + if (tot_views_length) + handle_request_for_tables(views + 1, tot_views_length - 1, TRUE, opt_all_in_1); + my_free(tables); + my_free(views); + } + else + { + while ((row = mysql_fetch_row(res))) + { + /* Skip views if we don't perform renaming. */ + if ((what_to_do != DO_FIX_NAMES) && (num_columns == 2) && (strcmp(row[1], "VIEW") == 0)) + { + if (!opt_do_views) + continue; + view= TRUE; + } + else + { + if (!opt_do_tables) + continue; + view= FALSE; + } + if (system_database && + (!strcmp(row[0], "general_log") || + !strcmp(row[0], "slow_log"))) + continue; /* Skip logging tables */ + + handle_request_for_tables(row[0], fixed_name_length(row[0]), view, opt_all_in_1); + } + } + mysql_free_result(res); + DBUG_RETURN(0); +} /* process_all_tables_in_db */ + + +static int run_query(const char *query, my_bool log_query) +{ + if (verbose >=3 && log_query) + puts(query); + if (mysql_query(sock, query)) + { + fprintf(stderr, "Failed to %s\n", query); + fprintf(stderr, "Error: %s\n", mysql_error(sock)); + return 1; + } + return 0; +} + + +static int fix_table_storage_name(const char *name) +{ + char qbuf[100 + NAME_LEN*4]; + int rc= 0; + DBUG_ENTER("fix_table_storage_name"); + + if (strncmp(name, "#mysql50#", 9)) + DBUG_RETURN(1); + my_snprintf(qbuf, sizeof(qbuf), "RENAME TABLE %`s TO %`s", + name, name + 9); + + rc= run_query(qbuf, 1); + if (verbose) + printf("%-50s %s\n", name, rc ? "FAILED" : "OK"); + DBUG_RETURN(rc); +} + +static int fix_database_storage_name(const char *name) +{ + char qbuf[100 + NAME_LEN*4]; + int rc= 0; + DBUG_ENTER("fix_database_storage_name"); + + if (strncmp(name, "#mysql50#", 9)) + DBUG_RETURN(1); + my_snprintf(qbuf, sizeof(qbuf), "ALTER DATABASE %`s UPGRADE DATA DIRECTORY " + "NAME", name); + rc= run_query(qbuf, 1); + if (verbose) + printf("%-50s %s\n", name, rc ? "FAILED" : "OK"); + DBUG_RETURN(rc); +} + +static int rebuild_table(char *name) +{ + char *query, *ptr; + int rc= 0; + DBUG_ENTER("rebuild_table"); + + query= (char*)my_malloc(PSI_NOT_INSTRUMENTED, 12+strlen(name)+6+1, MYF(MY_WME)); + if (!query) + DBUG_RETURN(1); + ptr= strxmov(query, "ALTER TABLE ", name, " FORCE", NullS); + if (verbose >= 3) + puts(query); + if (mysql_real_query(sock, query, (ulong)(ptr - query))) + { + fprintf(stderr, "Failed to %s\n", query); + fprintf(stderr, "Error: %s\n", mysql_error(sock)); + rc= 1; + } + if (verbose) + printf("%-50s %s\n", name, rc ? "FAILED" : "FIXED"); + my_free(query); + DBUG_RETURN(rc); +} + +static int process_one_db(char *database) +{ + DBUG_ENTER("process_one_db"); + + if (opt_skip_database && !strcmp(database, opt_skip_database)) + DBUG_RETURN(0); + + if (verbose) + puts(database); + if (what_to_do == DO_FIX_NAMES) + { + int rc= 0; + if (opt_fix_db_names && !strncmp(database,"#mysql50#", 9)) + { + rc= fix_database_storage_name(database); + database+= 9; + } + if (rc || !opt_fix_table_names) + DBUG_RETURN(rc); + } + DBUG_RETURN(process_all_tables_in_db(database)); +} + + +static int use_db(char *database) +{ + DBUG_ENTER("use_db"); + + if (mysql_get_server_version(sock) >= FIRST_INFORMATION_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, database, INFORMATION_SCHEMA_DB_NAME)) + DBUG_RETURN(1); + if (mysql_get_server_version(sock) >= FIRST_PERFORMANCE_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, database, PERFORMANCE_SCHEMA_DB_NAME)) + DBUG_RETURN(1); + if (mysql_select_db(sock, database)) + { + DBerror(sock, "when selecting the database"); + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} /* use_db */ + +/* Do not send commands to replication slaves. */ +static int disable_binlog() +{ + mysql_query(sock, "SET WSREP_ON=0"); /* ignore the error, if any */ + return run_query("SET SQL_LOG_BIN=0", 0); +} + +static int handle_request_for_tables(char *tables, size_t length, + my_bool view, my_bool dont_quote) +{ + char *query, *end, options[100], message[100]; + char table_name_buff[NAME_CHAR_LEN*2*2+1], *table_name; + size_t query_length= 0, query_size= sizeof(char)*(length+110); + const char *op = 0; + const char *tab_view; + DBUG_ENTER("handle_request_for_tables"); + + options[0] = 0; + tab_view= view ? " VIEW " : " TABLE "; + end = options; + switch (what_to_do) { + case DO_CHECK: + op = "CHECK"; + if (view) + { + if (opt_fast || opt_check_only_changed) + DBUG_RETURN(0); + } + else + { + if (opt_quick) end = strmov(end, " QUICK"); + if (opt_fast) end = strmov(end, " FAST"); + if (opt_extended) end = strmov(end, " EXTENDED"); + if (opt_medium_check) end = strmov(end, " MEDIUM"); /* Default */ + if (opt_check_only_changed) end = strmov(end, " CHANGED"); + } + if (opt_upgrade) end = strmov(end, " FOR UPGRADE"); + break; + case DO_REPAIR: + op= opt_write_binlog ? "REPAIR" : "REPAIR NO_WRITE_TO_BINLOG"; + if (view) + { + if (opt_do_views == DO_VIEWS_FROM_MYSQL) + end = strmov(end, " FROM MYSQL"); + else if (opt_do_views == DO_UPGRADE) + end = strmov(end, " FOR UPGRADE"); + } + else + { + if (opt_quick) end = strmov(end, " QUICK"); + if (opt_extended) end = strmov(end, " EXTENDED"); + if (opt_frm) end = strmov(end, " USE_FRM"); + } + break; + case DO_ANALYZE: + if (view) + { + printf("%-50s %s\n", tables, "Can't run analyze on a view"); + DBUG_RETURN(1); + } + DBUG_ASSERT(!view); + op= (opt_write_binlog) ? "ANALYZE" : "ANALYZE NO_WRITE_TO_BINLOG"; + if (opt_persistent_all) end = strmov(end, " PERSISTENT FOR ALL"); + break; + case DO_OPTIMIZE: + if (view) + { + printf("%-50s %s\n", tables, "Can't run optimize on a view"); + DBUG_RETURN(1); + } + op= (opt_write_binlog) ? "OPTIMIZE" : "OPTIMIZE NO_WRITE_TO_BINLOG"; + break; + case DO_FIX_NAMES: + if (view) + { + printf("%-50s %s\n", tables, "Can't run fix names on a view"); + DBUG_RETURN(1); + } + DBUG_RETURN(fix_table_storage_name(tables)); + } + + if (!(query =(char *) my_malloc(PSI_NOT_INSTRUMENTED, query_size, MYF(MY_WME)))) + DBUG_RETURN(1); + if (dont_quote) + { + DBUG_ASSERT(op); + DBUG_ASSERT(strlen(op)+strlen(tables)+strlen(options)+8+1 <= query_size); + + /* No backticks here as we added them before */ + query_length= sprintf(query, "%s%s%s %s", op, + tab_view, tables, options); + table_name= tables; + } + else + { + char *ptr, *org; + + org= ptr= strmov(strmov(query, op), tab_view); + ptr= fix_table_name(ptr, tables); + strmake(table_name_buff, org, MY_MIN((int) sizeof(table_name_buff)-1, + (int) (ptr - org))); + table_name= table_name_buff; + ptr= strxmov(ptr, " ", options, NullS); + query_length= (size_t) (ptr - query); + } + if (verbose >= 3) + puts(query); + if (mysql_real_query(sock, query, (ulong)query_length)) + { + my_snprintf(message, sizeof(message), "when executing '%s%s... %s'", + op, tab_view, options); + DBerror(sock, message); + my_free(query); + DBUG_RETURN(1); + } + print_result(); + if (opt_flush_tables) + { + query_length= sprintf(query, "FLUSH TABLES %s", table_name); + if (mysql_real_query(sock, query, (ulong)query_length)) + { + DBerror(sock, query); + my_free(query); + DBUG_RETURN(1); + } + } + my_free(query); + DBUG_RETURN(0); +} + +static void insert_table_name(DYNAMIC_ARRAY *arr, char *in, size_t dblen) +{ + char buf[NAME_LEN*2+2]; + in[dblen]= 0; + my_snprintf(buf, sizeof(buf), "%`s.%`s", in, in + dblen + 1); + insert_dynamic(arr, (uchar*) buf); +} + +static void print_result() +{ + MYSQL_RES *res; + MYSQL_ROW row; + char prev[(NAME_LEN+9)*3+2]; + char prev_alter[MAX_ALTER_STR_SIZE]; + size_t length_of_db= strlen(sock->db); + my_bool found_error=0, table_rebuild=0; + DYNAMIC_ARRAY *array4repair= &tables4repair; + DBUG_ENTER("print_result"); + + res = mysql_use_result(sock); + + prev[0] = '\0'; + prev_alter[0]= 0; + while ((row = mysql_fetch_row(res))) + { + int changed = strcmp(prev, row[0]); + my_bool status = !strcmp(row[2], "status"); + + if (status) + { + /* + if there was an error with the table, we have --auto-repair set, + and this isn't a repair op, then add the table to the tables4repair + list + */ + if (found_error && opt_auto_repair && what_to_do != DO_REPAIR && + strcmp(row[3],"OK")) + { + if (table_rebuild) + { + if (prev_alter[0]) + insert_dynamic(&alter_table_cmds, (uchar*) prev_alter); + else + insert_table_name(&tables4rebuild, prev, length_of_db); + } + else + insert_table_name(array4repair, prev, length_of_db); + } + array4repair= &tables4repair; + found_error=0; + table_rebuild=0; + prev_alter[0]= 0; + if (opt_silent) + continue; + } + if (status && changed) + printf("%-50s %s", row[0], row[3]); + else if (!status && changed) + { + /* + If the error message includes REPAIR TABLE, we assume it means + we have to run upgrade on it. In this case we write a nicer message + than "Please do "REPAIR TABLE""... + */ + if (!strcmp(row[2],"error") && strstr(row[3],"REPAIR ")) + { + printf("%-50s %s", row[0], "Needs upgrade"); + array4repair= strstr(row[3], "VIEW") ? &views4repair : &tables4repair; + } + else + printf("%s\n%-9s: %s", row[0], row[2], row[3]); + if (opt_auto_repair && strcmp(row[2],"note")) + { + found_error=1; + if (opt_auto_repair && strstr(row[3], "ALTER TABLE") != NULL) + table_rebuild=1; + } + } + else + printf("%-9s: %s", row[2], row[3]); + strmov(prev, row[0]); + putchar('\n'); + } + /* add the last table to be repaired to the list */ + if (found_error && opt_auto_repair && what_to_do != DO_REPAIR) + { + if (table_rebuild) + { + if (prev_alter[0]) + insert_dynamic(&alter_table_cmds, prev_alter); + else + insert_table_name(&tables4rebuild, prev, length_of_db); + } + else + insert_table_name(array4repair, prev, length_of_db); + } + mysql_free_result(res); + DBUG_VOID_RETURN; +} + + +static int dbConnect(char *host, char *user, char *passwd) +{ + my_bool reconnect= 1; + DBUG_ENTER("dbConnect"); + if (verbose > 1) + { + fprintf(stderr, "# Connecting to %s...\n", host ? host : "localhost"); + } + mysql_init(&mysql_connection); + if (opt_compress) + mysql_options(&mysql_connection, MYSQL_OPT_COMPRESS, NullS); +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(&mysql_connection, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(&mysql_connection, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(&mysql_connection, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(&mysql_connection,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql_connection, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql_connection, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(&mysql_connection, MYSQL_SET_CHARSET_NAME, default_charset); + mysql_options(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqlcheck"); + if (!(sock = mysql_real_connect(&mysql_connection, host, user, passwd, + NULL, opt_mysql_port, opt_mysql_unix_port, 0))) + { + DBerror(&mysql_connection, "when trying to connect"); + DBUG_RETURN(1); + } + mysql_options(&mysql_connection, MYSQL_OPT_RECONNECT, &reconnect); + DBUG_RETURN(0); +} /* dbConnect */ + + +static void dbDisconnect(char *host) +{ + DBUG_ENTER("dbDisconnect"); + if (verbose > 1) + fprintf(stderr, "# Disconnecting from %s...\n", host ? host : "localhost"); + mysql_close(sock); + DBUG_VOID_RETURN; +} /* dbDisconnect */ + + +static void DBerror(MYSQL *mysql, const char *when) +{ + DBUG_ENTER("DBerror"); + my_printf_error(0,"Got error: %d: %s %s", MYF(0), + mysql_errno(mysql), mysql_error(mysql), when); + safe_exit(EX_MYSQLERR); + DBUG_VOID_RETURN; +} /* DBerror */ + + +static void safe_exit(int error) +{ + DBUG_ENTER("safe_exit"); + if (!first_error) + first_error= error; + if (ignore_errors) + DBUG_VOID_RETURN; + if (sock) + mysql_close(sock); + sf_leaking_memory= 1; /* don't check for memory leaks */ + exit(error); + DBUG_VOID_RETURN; +} + + +int main(int argc, char **argv) +{ + int ret= EX_USAGE; + char **defaults_argv; + + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + /* + ** Check out the args + */ + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv= argv; + if (get_options(&argc, &argv)) + goto end1; + + sf_leaking_memory=0; /* from now on we cleanup properly */ + + ret= EX_MYSQLERR; + if (dbConnect(current_host, current_user, opt_password)) + goto end1; + + ret= 1; + if (!opt_write_binlog) + { + if (disable_binlog()) + goto end; + } + + if (opt_auto_repair && + (my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &tables4repair, + NAME_LEN*2+2, 16, 64, MYF(0)) || + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &views4repair, + NAME_LEN*2+2, 16, 64, MYF(0)) || + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &tables4rebuild, + NAME_LEN*2+2, 16, 64, MYF(0)) || + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &alter_table_cmds, + MAX_ALTER_STR_SIZE, 0, 1, MYF(0)))) + goto end; + + if (opt_alldbs) + process_all_databases(); + /* Only one database and selected table(s) */ + else if (argc > 1 && !opt_databases) + process_selected_tables(*argv, (argv + 1), (argc - 1)); + /* One or more databases, all tables */ + else + process_databases(argv); + if (opt_auto_repair) + { + size_t i; + + if (!opt_silent && (tables4repair.elements || tables4rebuild.elements)) + puts("\nRepairing tables"); + what_to_do = DO_REPAIR; + for (i = 0; i < tables4repair.elements ; i++) + { + char *name= (char*) dynamic_array_ptr(&tables4repair, i); + handle_request_for_tables(name, fixed_name_length(name), FALSE, TRUE); + } + for (i = 0; i < tables4rebuild.elements ; i++) + rebuild_table((char*) dynamic_array_ptr(&tables4rebuild, i)); + for (i = 0; i < alter_table_cmds.elements ; i++) + run_query((char*) dynamic_array_ptr(&alter_table_cmds, i), 1); + if (!opt_silent && views4repair.elements) + puts("\nRepairing views"); + for (i = 0; i < views4repair.elements ; i++) + { + char *name= (char*) dynamic_array_ptr(&views4repair, i); + handle_request_for_tables(name, fixed_name_length(name), TRUE, TRUE); + } + } + ret= MY_TEST(first_error); + + end: + dbDisconnect(current_host); + if (opt_auto_repair) + { + delete_dynamic(&views4repair); + delete_dynamic(&tables4repair); + delete_dynamic(&tables4rebuild); + delete_dynamic(&alter_table_cmds); + } + end1: + my_free(opt_password);; + mysql_library_end(); + free_defaults(defaults_argv); + my_end(my_end_arg); + return ret; +} /* main */ diff --git a/client/mysqldump.c b/client/mysqldump.c new file mode 100644 index 00000000..0a6ebf0e --- /dev/null +++ b/client/mysqldump.c @@ -0,0 +1,7292 @@ +/* + Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2020, MariaDB Corporation. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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 Street, Fifth Floor, Boston, MA 02110-1335 USA +*/ + +/* mysqldump.c - Dump a tables contents and format to an ASCII file +** +** The author's original notes follow :- +** +** AUTHOR: Igor Romanenko (igor@frog.kiev.ua) +** DATE: December 3, 1994 +** WARRANTY: None, expressed, impressed, implied +** or other +** STATUS: Public domain +** Adapted and optimized for MySQL by +** Michael Widenius, Sinisa Milivojevic, Jani Tolonen +** -w --where added 9/10/98 by Jim Faucette +** slave code by David Saez Padros +** master/autocommit code by Brian Aker +** SSL by +** Andrei Errapart +** Tõnu Samuel +** XML by Gary Huntress 10/10/01, cleaned up +** and adapted to mysqldump 05/11/01 by Jani Tolonen +** Added --single-transaction option 06/06/2002 by Peter Zaitsev +** 10 Jun 2003: SET NAMES and --no-set-names by Alexander Barkov +*/ + +/* on merge conflict, bump to a higher version again */ +#define DUMP_VERSION "10.19" + +/** + First mysql version supporting sequences. +*/ +#define FIRST_SEQUENCE_VERSION 100300 + +#include +#include +#include +#include +#include +#include +#include + +#include "client_priv.h" +#include "mysql.h" +#include "mysql_version.h" +#include "mysqld_error.h" + +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +/* Exit codes */ + +#define EX_USAGE 1 +#define EX_MYSQLERR 2 +#define EX_CONSCHECK 3 +#define EX_EOM 4 +#define EX_EOF 5 /* ferror for output file was got */ +#define EX_ILLEGAL_TABLE 6 + +/* index into 'show fields from table' */ + +#define SHOW_FIELDNAME 0 +#define SHOW_TYPE 1 +#define SHOW_NULL 2 +#define SHOW_DEFAULT 4 +#define SHOW_EXTRA 5 + +/* Size of buffer for dump's select query */ +#define QUERY_LENGTH 1536 + +/* Size of comment buffer. */ +#define COMMENT_LENGTH 2048 + +/* ignore table flags */ +#define IGNORE_NONE 0x00 /* no ignore */ +#define IGNORE_DATA 0x01 /* don't dump data for this table */ +#define IGNORE_INSERT_DELAYED 0x02 /* table doesn't support INSERT DELAYED */ +#define IGNORE_SEQUENCE_TABLE 0x04 /* catch the SEQUENCE*/ +#define IGNORE_S3_TABLE 0x08 + +/* Chars needed to store LONGLONG, excluding trailing '\0'. */ +#define LONGLONG_LEN 20 + +/* Max length GTID position that we will output. */ +#define MAX_GTID_LENGTH 1024 + +/* Dump sequence/tables control */ +#define DUMP_TABLE_ALL -1 +#define DUMP_TABLE_TABLE 0 +#define DUMP_TABLE_SEQUENCE 1 + +static my_bool ignore_table_data(const uchar *hash_key, size_t len); +static void add_load_option(DYNAMIC_STRING *str, const char *option, + const char *option_value); +static ulong find_set(TYPELIB *, const char *, size_t, char **, uint *); +static char *alloc_query_str(size_t size); + +static void field_escape(DYNAMIC_STRING* in, const char *from); +static my_bool verbose= 0, opt_no_create_info= 0, opt_no_data= 0, opt_no_data_med= 1, + quick= 1, extended_insert= 1, + lock_tables=1,ignore_errors=0,flush_logs=0,flush_privileges=0, + opt_drop=1,opt_keywords=0,opt_lock=1,opt_compress=0, + opt_copy_s3_tables=0, + opt_delayed=0,create_options=1,opt_quoted=0,opt_databases=0, + opt_alldbs=0,opt_create_db=0,opt_lock_all_tables=0, + opt_set_charset=0, opt_dump_date=1, + opt_autocommit=0,opt_disable_keys=1,opt_xml=0, + opt_delete_master_logs=0, tty_password=0, + opt_single_transaction=0, opt_comments= 0, opt_compact= 0, + opt_hex_blob=0, opt_order_by_primary=0, opt_order_by_size = 0, + opt_ignore=0, opt_complete_insert= 0, opt_drop_database= 0, + opt_replace_into= 0, + opt_dump_triggers= 0, opt_routines=0, opt_tz_utc=1, + opt_slave_apply= 0, + opt_include_master_host_port= 0, + opt_events= 0, opt_comments_used= 0, + opt_alltspcs=0, opt_notspcs= 0, opt_logging, + opt_header=0, + opt_drop_trigger= 0, opt_dump_history= 0; +#define OPT_SYSTEM_ALL 1 +#define OPT_SYSTEM_USERS 2 +#define OPT_SYSTEM_PLUGINS 4 +#define OPT_SYSTEM_UDFS 8 +#define OPT_SYSTEM_SERVERS 16 +#define OPT_SYSTEM_STATS 32 +#define OPT_SYSTEM_TIMEZONES 64 +static const char *opt_system_type_values[]= + {"all", "users", "plugins", "udfs", "servers", "stats", "timezones"}; +static TYPELIB opt_system_types= +{ + array_elements(opt_system_type_values), "system dump options", + opt_system_type_values, NULL +}; +static ulonglong opt_system= 0ULL; +static my_bool insert_pat_inited= 0, debug_info_flag= 0, debug_check_flag= 0, + select_field_names_inited= 0; +static ulong opt_max_allowed_packet, opt_net_buffer_length; +static double opt_max_statement_time= 0.0; +static MYSQL mysql_connection,*mysql=0; +static DYNAMIC_STRING insert_pat, select_field_names, select_field_names_for_header; +static char *opt_password=0,*current_user=0, + *current_host=0,*path=0,*fields_terminated=0, + *lines_terminated=0, *enclosed=0, *opt_enclosed=0, *escaped=0, + *where=0, *order_by=0, + *err_ptr= 0, + *log_error_file= NULL, *opt_asof_timestamp= NULL; +static const char *opt_compatible_mode_str= 0; +static char **defaults_argv= 0; +static char compatible_mode_normal_str[255]; +/* Server supports character_set_results session variable? */ +static my_bool server_supports_switching_charsets= TRUE; +static ulong opt_compatible_mode= 0; +#define MYSQL_OPT_MASTER_DATA_EFFECTIVE_SQL 1 +#define MYSQL_OPT_MASTER_DATA_COMMENTED_SQL 2 +#define MYSQL_OPT_MAX_STATEMENT_TIME 0 +#define MYSQL_OPT_SLAVE_DATA_EFFECTIVE_SQL 1 +#define MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL 2 +static uint opt_mysql_port= 0, opt_master_data; +static uint opt_slave_data; +static uint opt_use_gtid; +static uint my_end_arg; +static char * opt_mysql_unix_port=0; +static int first_error=0; +/* + multi_source is 0 if old server or 2 if server that support multi source + This is chosen this was as multi_source has 2 extra columns first in + SHOW ALL SLAVES STATUS. +*/ +static uint multi_source= 0; +static DYNAMIC_STRING extended_row; +static DYNAMIC_STRING dynamic_where; +static MYSQL_RES *get_table_name_result= NULL; +static MEM_ROOT glob_root; +static MYSQL_RES *routine_res, *routine_list_res; + + +#include +FILE *md_result_file= 0; +FILE *stderror_file=0; + +static uint opt_protocol= 0; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; + +/* + Dynamic_string wrapper functions. In this file use these + wrappers, they will terminate the process if there is + an allocation failure. +*/ +static void init_dynamic_string_checked(DYNAMIC_STRING *str, const char *init_str, + size_t init_alloc, size_t alloc_increment); +static void dynstr_append_checked(DYNAMIC_STRING* dest, const char* src); +static void dynstr_set_checked(DYNAMIC_STRING *str, const char *init_str); +static void dynstr_append_mem_checked(DYNAMIC_STRING *str, const char *append, + uint length); +static void dynstr_realloc_checked(DYNAMIC_STRING *str, ulong additional_size); + +static int do_start_slave_sql(MYSQL *mysql_con); +/* + Constant for detection of default value of default_charset. + If default_charset is equal to mysql_universal_client_charset, then + it is the default value which assigned at the very beginning of main(). +*/ +static const char *mysql_universal_client_charset= + MYSQL_UNIVERSAL_CLIENT_CHARSET; +static char *default_charset; +static CHARSET_INFO *charset_info= &my_charset_latin1; +const char *default_dbug_option="d:t:o,/tmp/mariadb-dump.trace"; +/* have we seen any VIEWs during table scanning? */ +my_bool seen_views= 0; +const char *compatible_mode_names[]= +{ + "MYSQL323", "MYSQL40", "POSTGRESQL", "ORACLE", "MSSQL", "DB2", + "MAXDB", "NO_KEY_OPTIONS", "NO_TABLE_OPTIONS", "NO_FIELD_OPTIONS", + "ANSI", + NullS +}; +#define MASK_ANSI_QUOTES \ +(\ + (1U<<2) | /* POSTGRESQL */\ + (1U<<3) | /* ORACLE */\ + (1U<<4) | /* MSSQL */\ + (1U<<5) | /* DB2 */\ + (1U<<6) | /* MAXDB */\ + (1U<<10) /* ANSI */\ +) +TYPELIB compatible_mode_typelib= {array_elements(compatible_mode_names) - 1, + "", compatible_mode_names, NULL}; + +#define MED_ENGINES "MRG_MyISAM, MRG_ISAM, CONNECT, OQGRAPH, SPIDER, VP, FEDERATED" + +static HASH ignore_table, ignore_data; + +static HASH ignore_database; + +static struct my_option my_long_options[] = +{ + {"all-databases", 'A', + "Dump all the databases. This will be same as --databases with all databases selected.", + &opt_alldbs, &opt_alldbs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"all-tablespaces", 'Y', + "Dump all the tablespaces.", + &opt_alltspcs, &opt_alltspcs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"no-tablespaces", 'y', + "Do not dump any tablespace information.", + &opt_notspcs, &opt_notspcs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"add-drop-database", OPT_DROP_DATABASE, "Add a DROP DATABASE before each create.", + &opt_drop_database, &opt_drop_database, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"add-drop-table", OPT_DROP, "Add a DROP TABLE before each create.", + &opt_drop, &opt_drop, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, + 0}, + {"add-drop-trigger", 0, "Add a DROP TRIGGER before each create.", + &opt_drop_trigger, &opt_drop_trigger, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, + 0}, + {"add-locks", OPT_LOCKS, "Add locks around INSERT statements.", + &opt_lock, &opt_lock, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, + 0}, + {"allow-keywords", OPT_KEYWORDS, + "Allow creation of column names that are keywords.", &opt_keywords, + &opt_keywords, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"apply-slave-statements", OPT_MYSQLDUMP_SLAVE_APPLY, + "Adds 'STOP SLAVE' prior to 'CHANGE MASTER' and 'START SLAVE' to bottom of dump.", + &opt_slave_apply, &opt_slave_apply, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"as-of", OPT_ASOF_TIMESTAMP, + "Dump system versioned table(s) as of specified timestamp. " + "Argument is interpreted according to the --tz-utc setting. " + "Table structures are always dumped as of current timestamp.", + &opt_asof_timestamp, &opt_asof_timestamp, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", (char **)&charsets_dir, + (char **)&charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"comments", 'i', "Write additional information.", + &opt_comments, &opt_comments, 0, GET_BOOL, NO_ARG, + 1, 0, 0, 0, 0, 0}, + {"compatible", OPT_COMPATIBLE, + "Change the dump to be compatible with a given mode. By default tables " + "are dumped in a format optimized for MariaDB. Legal modes are: ansi, " + "mysql323, mysql40, postgresql, oracle, mssql, db2, maxdb, no_key_options, " + "no_table_options, no_field_options. One can use several modes separated " + "by commas. Note: Requires MariaDB server version 4.1.0 or higher. " + "This option is ignored with earlier server versions.", + (char**) &opt_compatible_mode_str, (char**) &opt_compatible_mode_str, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"compact", OPT_COMPACT, + "Give less verbose output (useful for debugging). Disables structure " + "comments and header/footer constructs. Enables options --skip-add-" + "drop-table --skip-add-locks --skip-comments --skip-disable-keys " + "--skip-set-charset.", + &opt_compact, &opt_compact, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"complete-insert", 'c', "Use complete insert statements.", + &opt_complete_insert, &opt_complete_insert, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"copy_s3_tables", OPT_COPY_S3_TABLES, + "If 'no' S3 tables will be ignored, otherwise S3 tables will be copied as " + " Aria tables and then altered to S3", + &opt_copy_s3_tables, &opt_copy_s3_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"create-options", 'a', + "Include all MariaDB specific create options.", + &create_options, &create_options, 0, GET_BOOL, NO_ARG, 1, + 0, 0, 0, 0, 0}, + {"databases", 'B', + "Dump several databases. Note the difference in usage; in this case no tables are given. All name arguments are regarded as database names. 'USE db_name;' will be included in the output.", + &opt_databases, &opt_databases, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0,0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log.", (char *)&default_dbug_option, + (char *)&default_dbug_option, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"delayed-insert", OPT_DELAYED, "Insert rows with INSERT DELAYED.", + &opt_delayed, &opt_delayed, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"delete-master-logs", OPT_DELETE_MASTER_LOGS, + "Delete logs on master after backup. This automatically enables --master-data.", + &opt_delete_master_logs, &opt_delete_master_logs, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"disable-keys", 'K', + "'/*!40000 ALTER TABLE tb_name DISABLE KEYS */; and '/*!40000 ALTER " + "TABLE tb_name ENABLE KEYS */; will be put in the output.", &opt_disable_keys, + &opt_disable_keys, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"dump-date", OPT_DUMP_DATE, "Put a dump date to the end of the output.", + &opt_dump_date, &opt_dump_date, 0, + GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"dump-history", 'H', "Dump system-versioned tables with history (only for " + "timestamp based versioning)", &opt_dump_history, + &opt_dump_history, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"dump-slave", OPT_MYSQLDUMP_SLAVE_DATA, + "This causes the binary log position and filename of the master to be " + "appended to the dumped data output. Setting the value to 1, will print" + "it as a CHANGE MASTER command in the dumped data output; if equal" + " to 2, that command will be prefixed with a comment symbol. " + "This option will turn --lock-all-tables on, unless " + "--single-transaction is specified too (in which case a " + "global read lock is only taken a short time at the beginning of the dump " + "- don't forget to read about --single-transaction below). In all cases " + "any action on logs will happen at the exact moment of the dump." + "Option automatically turns --lock-tables off.", + &opt_slave_data, &opt_slave_data, 0, + GET_UINT, OPT_ARG, 0, 0, MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL, 0, 0, 0}, + {"events", 'E', "Dump events.", + &opt_events, &opt_events, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"extended-insert", 'e', + "Use multiple-row INSERT syntax that include several VALUES lists.", + &extended_insert, &extended_insert, 0, GET_BOOL, NO_ARG, + 1, 0, 0, 0, 0, 0}, + {"fields-terminated-by", OPT_FTB, + "Fields in the output file are terminated by the given string.", + &fields_terminated, &fields_terminated, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"fields-enclosed-by", OPT_ENC, + "Fields in the output file are enclosed by the given character.", + &enclosed, &enclosed, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0 ,0, 0}, + {"fields-optionally-enclosed-by", OPT_O_ENC, + "Fields in the output file are optionally enclosed by the given character.", + &opt_enclosed, &opt_enclosed, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0 ,0, 0}, + {"fields-escaped-by", OPT_ESC, + "Fields in the output file are escaped by the given character.", + &escaped, &escaped, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"flush-logs", 'F', "Flush logs file in server before starting dump. " + "Note that if you dump many databases at once (using the option " + "--databases= or --all-databases), the logs will be flushed for " + "each database dumped. The exception is when using --lock-all-tables " + "or --master-data: " + "in this case the logs will be flushed only once, corresponding " + "to the moment all tables are locked. So if you want your dump and " + "the log flush to happen at the same exact moment you should use " + "--lock-all-tables or --master-data with --flush-logs.", + &flush_logs, &flush_logs, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"flush-privileges", OPT_ESC, "Emit a FLUSH PRIVILEGES statement " + "after dumping the mysql database. This option should be used any " + "time the dump contains the mysql database and any other database " + "that depends on the data in the mysql database for proper restore. ", + &flush_privileges, &flush_privileges, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"force", 'f', "Continue even if we get an SQL error.", + &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"gtid", 0, "Used together with --master-data=1 or --dump-slave=1." + "When enabled, the output from those options will set the GTID position " + "instead of the binlog file and offset; the file/offset will appear only as " + "a comment. When disabled, the GTID position will still appear in the " + "output, but only commented.", + &opt_use_gtid, &opt_use_gtid, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"header", 0, "Used together with --tab. When enabled, adds header with column names to the top of output txt files.", + &opt_header, &opt_header, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help message and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"hex-blob", OPT_HEXBLOB, "Dump binary strings (BINARY, " + "VARBINARY, BLOB) in hexadecimal format.", + &opt_hex_blob, &opt_hex_blob, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", ¤t_host, + ¤t_host, 0, GET_STR_ALLOC, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ignore-database", OPT_IGNORE_DATABASE, + "Do not dump the specified database. To specify more than one database to ignore, " + "use the directive multiple times, once for each database. Only takes effect " + "when used together with --all-databases|-A", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ignore-table-data", OPT_IGNORE_DATA, + "Do not dump the specified table data. To specify more than one table " + "to ignore, use the directive multiple times, once for each table. " + "Each table must be specified with both database and table names, e.g., " + "--ignore-table-data=database.table.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ignore-table", OPT_IGNORE_TABLE, + "Do not dump the specified table. To specify more than one table to ignore, " + "use the directive multiple times, once for each table. Each table must " + "be specified with both database and table names, e.g., " + "--ignore-table=database.table.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"include-master-host-port", OPT_MYSQLDUMP_INCLUDE_MASTER_HOST_PORT, + "Adds 'MASTER_HOST=, MASTER_PORT=' to 'CHANGE MASTER TO..' " + "in dump produced with --dump-slave.", &opt_include_master_host_port, + &opt_include_master_host_port, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"insert-ignore", OPT_INSERT_IGNORE, "Insert rows with INSERT IGNORE.", + &opt_ignore, &opt_ignore, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"lines-terminated-by", OPT_LTB, + "Lines in the output file are terminated by the given string.", + &lines_terminated, &lines_terminated, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"lock-all-tables", 'x', "Locks all tables across all databases. This " + "is achieved by taking a global read lock for the duration of the whole " + "dump. Automatically turns --single-transaction and --lock-tables off.", + &opt_lock_all_tables, &opt_lock_all_tables, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"lock-tables", 'l', "Lock all tables for read.", &lock_tables, + &lock_tables, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"log-error", OPT_ERROR_LOG_FILE, "Append warnings and errors to given file.", + &log_error_file, &log_error_file, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"log-queries", 0, "When restoring the dump, the server will, if logging turned on, log the queries to the general and slow query log.", + &opt_logging, &opt_logging, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"master-data", OPT_MASTER_DATA, + "This causes the binary log position and filename to be appended to the " + "output. If equal to 1, will print it as a CHANGE MASTER command; if equal" + " to 2, that command will be prefixed with a comment symbol. " + "This option will turn --lock-all-tables on, unless --single-transaction " + "is specified too (on servers before MariaDB 5.3 this will still take a " + "global read lock for a short time at the beginning of the dump; " + "don't forget to read about --single-transaction below). In all cases, " + "any action on logs will happen at the exact moment of the dump. " + "Option automatically turns --lock-tables off.", + &opt_master_data, &opt_master_data, 0, + GET_UINT, OPT_ARG, 0, 0, MYSQL_OPT_MASTER_DATA_COMMENTED_SQL, 0, 0, 0}, + {"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, 0, + GET_ULONG, REQUIRED_ARG, 24*1024*1024, 4096, + (longlong) 2L*1024L*1024L*1024L, MALLOC_OVERHEAD, 1024, 0}, + {"max-statement-time", MYSQL_OPT_MAX_STATEMENT_TIME, + "Max statement execution time. If unset, overrides server default with 0.", + &opt_max_statement_time, &opt_max_statement_time, 0, GET_DOUBLE, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"net_buffer_length", OPT_NET_BUFFER_LENGTH, + "The buffer size for TCP/IP and socket communication.", + &opt_net_buffer_length, &opt_net_buffer_length, 0, + GET_ULONG, REQUIRED_ARG, 1024*1024L-1025, 4096, 16*1024L*1024L, + MALLOC_OVERHEAD-1024, 1024, 0}, + {"no-autocommit", OPT_AUTOCOMMIT, + "Wrap tables with autocommit/commit statements.", + &opt_autocommit, &opt_autocommit, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"no-create-db", 'n', + "Suppress the CREATE DATABASE ... IF EXISTS statement that normally is " + "output for each dumped database if --all-databases or --databases is " + "given.", + &opt_create_db, &opt_create_db, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"no-create-info", 't', "Don't write table creation info.", + &opt_no_create_info, &opt_no_create_info, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"no-data", 'd', "No row information.", &opt_no_data, + &opt_no_data, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"no-data-med", 0, "No row information for engines that " + "Manage External Data (" MED_ENGINES ").", &opt_no_data_med, + &opt_no_data_med, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"no-set-names", 'N', "Same as --skip-set-charset.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"opt", OPT_OPTIMIZE, + "Same as --add-drop-table, --add-locks, --create-options, --quick, --extended-insert, --lock-tables, --set-charset, and --disable-keys. Enabled by default, disable with --skip-opt.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"order-by-primary", OPT_ORDER_BY_PRIMARY, + "Sorts each table's rows by primary key, or first unique key, if such a key exists. Useful when dumping a MyISAM table to be loaded into an InnoDB table, but will make the dump itself take considerably longer.", + &opt_order_by_primary, &opt_order_by_primary, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"order-by-size", 0, + "Dump tables in the order of their size, smaller first. Useful when using --single-transaction on tables which get truncated often. " + "Dumping smaller tables first reduces chances of often truncated tables to get altered before being dumped.", + &opt_order_by_size, &opt_order_by_size, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given it's solicited on the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"port", 'P', "Port number to use for connection.", &opt_mysql_port, + &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"quick", 'q', "Don't buffer query, dump directly to stdout.", + &quick, &quick, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, + {"quote-names",'Q', "Quote table and column names with backticks (`).", + &opt_quoted, &opt_quoted, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, + 0, 0}, + {"replace", OPT_MYSQL_REPLACE_INTO, "Use REPLACE INTO instead of INSERT INTO.", + &opt_replace_into, &opt_replace_into, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"result-file", 'r', + "Direct output to a given file. This option should be used in systems " + "(e.g., DOS, Windows) that use carriage-return linefeed pairs (\\r\\n) " + "to separate text lines. This option ensures that only a single newline " + "is used.", 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"routines", 'R', "Dump stored routines (functions and procedures).", + &opt_routines, &opt_routines, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"set-charset", OPT_SET_CHARSET, + "Add 'SET NAMES default_character_set' to the output.", + &opt_set_charset, &opt_set_charset, 0, GET_BOOL, NO_ARG, 1, + 0, 0, 0, 0, 0}, + /* + Note that the combination --single-transaction --master-data + will give bullet-proof binlog position only if server >=4.1.3. That's the + old "FLUSH TABLES WITH READ LOCK does not block commit" fixed bug. + */ + {"single-transaction", OPT_TRANSACTION, + "Creates a consistent snapshot by dumping all tables in a single " + "transaction. Works ONLY for tables stored in storage engines which " + "support multiversioning (currently only InnoDB does); the dump is NOT " + "guaranteed to be consistent for other storage engines. " + "While a --single-transaction dump is in process, to ensure a valid " + "dump file (correct table contents and binary log position), no other " + "connection should use the following statements: ALTER TABLE, DROP " + "TABLE, RENAME TABLE, TRUNCATE TABLE, as consistent snapshot is not " + "isolated from them. Option automatically turns off --lock-tables.", + &opt_single_transaction, &opt_single_transaction, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"skip-opt", OPT_SKIP_OPTIMIZATION, + "Disable --opt. Disables --add-drop-table, --add-locks, --create-options, --quick, --extended-insert, --lock-tables, --set-charset, and --disable-keys.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include + {"system", 256, "Dump system tables as portable SQL", + &opt_system, &opt_system, &opt_system_types, GET_SET, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tab",'T', + "Create tab-separated textfile for each table to given path. (Create .sql " + "and .txt files.) NOTE: This only works if mysqldump is run on the same " + "machine as the mysqld server.", + &path, &path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tables", OPT_TABLES, "Overrides option --databases (-B).", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"triggers", OPT_TRIGGERS, "Dump triggers for each dumped table.", + &opt_dump_triggers, &opt_dump_triggers, 0, GET_BOOL, + NO_ARG, 1, 0, 0, 0, 0, 0}, + {"tz-utc", OPT_TZ_UTC, + "Set connection time zone to UTC before commencing the dump and add " + "SET TIME_ZONE=´+00:00´ to the top of the dump file.", + &opt_tz_utc, &opt_tz_utc, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", + ¤t_user, ¤t_user, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', "Print info about the various stages.", + &verbose, &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version",'V', "Output version information and exit.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"where", 'w', "Dump only selected records. Quotes are mandatory.", + &where, &where, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"xml", 'X', "Dump a database as well formed XML.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + +static const char *load_default_groups[]= +{ "mysqldump", "mariadb-dump", "client", "client-server", "client-mariadb", + 0 }; + +static void maybe_exit(int error); +static void die(int error, const char* reason, ...); +static void maybe_die(int error, const char* reason, ...); +static void write_header(FILE *sql_file, const char *db_name); +static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, + const char *prefix,const char *name, + int string_value); +static int dump_selected_tables(char *db, char **table_names, int tables); +static int dump_all_tables_in_db(char *db); +static int init_dumping_views(char *); +static int init_dumping_tables(char *); +static int init_dumping(char *, int init_func(char*)); +static int dump_databases(char **); +static int dump_all_databases(); +static int dump_all_users_roles_and_grants(); +static int dump_all_plugins(); +static int dump_all_udfs(); +static int dump_all_servers(); +static int dump_all_stats(); +static int dump_all_timezones(); +static char *quote_name(const char *name, char *buff, my_bool force); +char check_if_ignore_table(const char *table_name, char *table_type); +static char *primary_key_fields(const char *table_name); +static my_bool get_view_structure(char *table, char* db); +static my_bool dump_all_views_in_db(char *database); +static int dump_all_tablespaces(); +static int dump_tablespaces_for_tables(char *db, char **table_names, int tables); +static int dump_tablespaces_for_databases(char** databases); +static int dump_tablespaces(char* ts_where); +static void print_comment(FILE *, my_bool, const char *, ...); + +/* + Print the supplied message if in verbose mode + + SYNOPSIS + verbose_msg() + fmt format specifier + ... variable number of parameters +*/ + +static void verbose_msg(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("verbose_msg"); + + if (!verbose) + DBUG_VOID_RETURN; + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + fflush(stderr); + + DBUG_VOID_RETURN; +} + +/* + exit with message if ferror(file) + + SYNOPSIS + check_io() + file - checked file +*/ + +void check_io(FILE *file) +{ + if (ferror(file)) + die(EX_EOF, "Got errno %d on write", errno); +} + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname_short,DUMP_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} /* print_version */ + + +static void short_usage_sub(FILE *f) +{ + fprintf(f, "Usage: %s [OPTIONS] database [tables]\n", my_progname_short); + fprintf(f, "OR %s [OPTIONS] --databases DB1 [DB2 DB3...]\n", + my_progname_short); + fprintf(f, "OR %s [OPTIONS] --all-databases\n", my_progname_short); + fprintf(f, "OR %s [OPTIONS] --system=[SYSTEMOPTIONS]]\n", my_progname_short); +} + + +static void usage(void) +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + puts("Dumping structure and contents of MariaDB databases and tables."); + short_usage_sub(stdout); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} /* usage */ + + +static void short_usage(FILE *f) +{ + short_usage_sub(f); + fprintf(f, "For more options, use %s --help\n", my_progname_short); +} + + +/** returns a string fixed to be safely printed inside a -- comment + + that is, any new line in it gets prefixed with -- +*/ +static const char *fix_for_comment(const char *ident) +{ + static char buf[1024]; + char c, *s= buf; + + while ((c= *s++= *ident++)) + { + if (s >= buf + sizeof(buf) - 10) + { + strmov(s, "..."); + break; + } + if (c == '\n') + s= strmov(s, "-- "); + } + + return buf; +} + + +static void write_header(FILE *sql_file, const char *db_name) +{ + if (opt_xml) + { + fputs("\n", sql_file); + /* + Schema reference. Allows use of xsi:nil for NULL values and + xsi:type to define an element's data type. + */ + fputs("\n", sql_file); + check_io(sql_file); + } + else if (!opt_compact) + { + print_comment(sql_file, 0, + "-- MariaDB dump %s Distrib %s, for %s (%s)\n--\n", + DUMP_VERSION, MYSQL_SERVER_VERSION, SYSTEM_TYPE, + MACHINE_TYPE); + print_comment(sql_file, 0, "-- Host: %s ", + fix_for_comment(current_host ? current_host : "localhost")); + print_comment(sql_file, 0, "Database: %s\n", + fix_for_comment(db_name ? db_name : "")); + print_comment(sql_file, 0, + "-- ------------------------------------------------------\n" + ); + print_comment(sql_file, 0, "-- Server version\t%s\n", + mysql_get_server_info(&mysql_connection)); + + if (!opt_logging) + fprintf(sql_file, +"\n/*M!100101 SET LOCAL SQL_LOG_OFF=0, LOCAL LOG_SLOW_QUERY=0 */;"); + + if (opt_set_charset) + fprintf(sql_file, +"\n/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;" +"\n/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;" +"\n/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;" +"\n/*!40101 SET NAMES %s */;\n",default_charset); + + if (opt_tz_utc) + { + fprintf(sql_file, "/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;\n"); + fprintf(sql_file, "/*!40103 SET TIME_ZONE='+00:00' */;\n"); + } + + if (!path) + { + if (!opt_no_create_info) + { + /* We don't need unique checks as the table is created just before */ + fprintf(md_result_file,"\ +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;\n"); + } + fprintf(md_result_file,"\ +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;\n\ +"); + } + fprintf(sql_file, + "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='%s%s%s' */;\n" + "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;\n", + path?"":"NO_AUTO_VALUE_ON_ZERO",compatible_mode_normal_str[0]==0?"":",", + compatible_mode_normal_str); + check_io(sql_file); + } +} /* write_header */ + + +static void write_footer(FILE *sql_file) +{ + if (opt_xml) + { + fputs("\n", sql_file); + check_io(sql_file); + } + else if (!opt_compact) + { + if (opt_tz_utc) + fprintf(sql_file,"/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;\n"); + + fprintf(sql_file,"\n/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;\n"); + if (!path) + { + fprintf(md_result_file,"\ +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;\n"); + if (!opt_no_create_info) + { + fprintf(md_result_file,"\ +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;\n"); + } + } + if (opt_set_charset) + fprintf(sql_file, +"/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;\n" +"/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;\n" +"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;\n"); + fprintf(sql_file, + "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;\n"); + fputs("\n", sql_file); + + if (opt_dump_date) + { + char time_str[20]; + get_date(time_str, GETDATE_DATE_TIME, 0); + print_comment(sql_file, 0, "-- Dump completed on %s\n", time_str); + } + else + print_comment(sql_file, 0, "-- Dump completed\n"); + + check_io(sql_file); + } +} /* write_footer */ + + +uchar* get_table_key(const char *entry, size_t *length, + my_bool not_used __attribute__((unused))) +{ + *length= strlen(entry); + return (uchar*) entry; +} + + +static my_bool +get_one_option(const struct my_option *opt, + const char *argument, + const char *filename) +{ + + switch (opt->id) { + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]=0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password=1; + break; + case 'r': + if (!(md_result_file= my_fopen(argument, O_WRONLY | FILE_BINARY, + MYF(MY_WME)))) + exit(1); + break; + case 'W': +#ifdef _WIN32 + opt_protocol= MYSQL_PROTOCOL_PIPE; +#endif + break; + case 'N': + opt_set_charset= 0; + break; + case 'T': + opt_disable_keys=0; + + if (strlen(argument) >= FN_REFLEN) + { + /* + This check is made because the some the file functions below + have FN_REFLEN sized stack allocated buffers and will cause + a crash even if the input destination buffer is large enough + to hold the output. + */ + die(EX_USAGE, "Input filename too long: %s", argument); + } + + break; + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + debug_check_flag= 1; + break; +#include + case 'V': print_version(); exit(0); + case 'X': + opt_xml= 1; + extended_insert= opt_drop= opt_lock= + opt_disable_keys= opt_autocommit= opt_create_db= 0; + break; + case 'i': + opt_comments_used= 1; + break; + case 'I': + case '?': + usage(); + exit(0); + case (int) OPT_MASTER_DATA: + if (!argument) /* work like in old versions */ + opt_master_data= MYSQL_OPT_MASTER_DATA_EFFECTIVE_SQL; + break; + case (int) OPT_MYSQLDUMP_SLAVE_DATA: + if (!argument) /* work like in old versions */ + opt_slave_data= MYSQL_OPT_SLAVE_DATA_EFFECTIVE_SQL; + break; + case (int) OPT_OPTIMIZE: + extended_insert= opt_drop= opt_lock= quick= create_options= + opt_disable_keys= lock_tables= opt_set_charset= 1; + break; + case (int) OPT_SKIP_OPTIMIZATION: + extended_insert= opt_drop= opt_lock= quick= create_options= + opt_disable_keys= lock_tables= opt_set_charset= 0; + break; + case (int) OPT_COMPACT: + if (opt_compact) + { + opt_comments= opt_drop= opt_disable_keys= opt_lock= 0; + opt_set_charset= 0; + } + break; + case (int) OPT_TABLES: + opt_databases=0; + break; + case (int) OPT_IGNORE_DATABASE: + if (my_hash_insert(&ignore_database, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(0)))) + exit(EX_EOM); + break; + case (int) OPT_IGNORE_DATA: + { + if (!strchr(argument, '.')) + { + fprintf(stderr, + "Illegal use of option --ignore-table-data=.\n"); + exit(1); + } + if (my_hash_insert(&ignore_data, (uchar*)my_strdup(PSI_NOT_INSTRUMENTED, + argument, MYF(0)))) + exit(EX_EOM); + break; + } + case (int) OPT_IGNORE_TABLE: + { + if (!strchr(argument, '.')) + { + fprintf(stderr, "Illegal use of option --ignore-table=.
\n"); + exit(1); + } + if (my_hash_insert(&ignore_table, + (uchar*)my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(0)))) + exit(EX_EOM); + break; + } + case (int) OPT_COMPATIBLE: + { + char buff[255]; + char *end= compatible_mode_normal_str; + int i; + ulong mode; + uint err_len; + + opt_quoted= 1; + opt_set_charset= 0; + opt_compatible_mode_str= argument; + opt_compatible_mode= find_set(&compatible_mode_typelib, + argument, strlen(argument), + &err_ptr, &err_len); + if (err_len) + { + strmake(buff, err_ptr, MY_MIN(sizeof(buff) - 1, err_len)); + fprintf(stderr, "Invalid mode to --compatible: %s\n", buff); + exit(1); + } +#if !defined(DBUG_OFF) + { + size_t size_for_sql_mode= 0; + const char **ptr; + for (ptr= compatible_mode_names; *ptr; ptr++) + size_for_sql_mode+= strlen(*ptr); + size_for_sql_mode+= sizeof(compatible_mode_names)-1; + DBUG_ASSERT(sizeof(compatible_mode_normal_str)>=size_for_sql_mode); + } +#endif + mode= opt_compatible_mode; + for (i= 0, mode= opt_compatible_mode; mode; mode>>= 1, i++) + { + if (mode & 1) + { + end= strmov(end, compatible_mode_names[i]); + end= strmov(end, ","); + } + } + if (end!=compatible_mode_normal_str) + end[-1]= 0; + /* + Set charset to the default compiled value if it hasn't + been reset yet by --default-character-set=xxx. + */ + if (default_charset == mysql_universal_client_charset) + default_charset= (char*) MYSQL_DEFAULT_CHARSET_NAME; + break; + } + case (int) OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case (int) OPT_DEFAULT_CHARSET: + if (default_charset == disabled_my_option) + default_charset= (char *)mysql_universal_client_charset; + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + } + return 0; +} + +static int get_options(int *argc, char ***argv) +{ + int ho_error; + MYSQL_PARAMETERS *mysql_params= mysql_get_parameters(); + + opt_max_allowed_packet= *mysql_params->p_max_allowed_packet; + opt_net_buffer_length= *mysql_params->p_net_buffer_length; + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + md_result_file= stdout; + load_defaults_or_exit("my", load_default_groups, argc, argv); + defaults_argv= *argv; + + if (my_hash_init(PSI_NOT_INSTRUMENTED, &ignore_database, charset_info, 16, 0, 0, + (my_hash_get_key) get_table_key, my_free, 0)) + return(EX_EOM); + if (my_hash_init(PSI_NOT_INSTRUMENTED, &ignore_table, charset_info, 16, 0, 0, + (my_hash_get_key) get_table_key, my_free, 0)) + return(EX_EOM); + /* Don't copy internal log tables */ + if (my_hash_insert(&ignore_table, (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.apply_status", MYF(MY_WME))) || + my_hash_insert(&ignore_table, (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.schema", MYF(MY_WME))) || + my_hash_insert(&ignore_table, (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.general_log", MYF(MY_WME))) || + my_hash_insert(&ignore_table, (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.slow_log", MYF(MY_WME))) || + my_hash_insert(&ignore_table, (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.transaction_registry", MYF(MY_WME)))) + return(EX_EOM); + + if (my_hash_init(PSI_NOT_INSTRUMENTED, &ignore_data, charset_info, 16, 0, 0, + (my_hash_get_key) get_table_key, my_free, 0)) + return(EX_EOM); + + if ((ho_error= handle_options(argc, argv, my_long_options, get_one_option))) + return(ho_error); + + /* + Dumping under --system=stats with --replace or --insert-ignore is + safe and will not result into race condition. Otherwise dump only + structure and ignore data by default while dumping. + */ + if (!(opt_system & OPT_SYSTEM_STATS) && !(opt_ignore || opt_replace_into)) + { + if (my_hash_insert(&ignore_data, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.innodb_index_stats", MYF(MY_WME))) || + my_hash_insert(&ignore_data, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.innodb_table_stats", MYF(MY_WME)))) + return(EX_EOM); + } + + if (opt_system & OPT_SYSTEM_ALL) + opt_system|= ~0; + + if (opt_system & OPT_SYSTEM_USERS && + (my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.db", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.global_priv", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.tables_priv", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.columns_priv", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.procs_priv", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.user", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.host", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.proxies_priv", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.roles_mapping", MYF(MY_WME))) || + /* and MySQL-8.0 role tables (role_edges and default_roles) as well */ + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.role_edges", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.default_roles", MYF(MY_WME))))) + return(EX_EOM); + + if (opt_system & OPT_SYSTEM_PLUGINS && + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.plugin", MYF(MY_WME)))) + return(EX_EOM); + + if (opt_system & OPT_SYSTEM_UDFS && + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.func", MYF(MY_WME)))) + return(EX_EOM); + + if (opt_system & OPT_SYSTEM_SERVERS && + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.servers", MYF(MY_WME)))) + return(EX_EOM); + + if (opt_system & OPT_SYSTEM_STATS && + (my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.column_stats", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.index_stats", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.table_stats", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.innodb_table_stats", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.innodb_index_stats", MYF(MY_WME))))) + return(EX_EOM); + + if (opt_system & OPT_SYSTEM_TIMEZONES && + (my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.time_zone", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.time_zone_leap_second", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.time_zone_name", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.time_zone_transition", MYF(MY_WME))) || + my_hash_insert(&ignore_table, + (uchar*) my_strdup(PSI_NOT_INSTRUMENTED, + "mysql.time_zone_transition_type", MYF(MY_WME))))) + return(EX_EOM); + + *mysql_params->p_max_allowed_packet= opt_max_allowed_packet; + *mysql_params->p_net_buffer_length= opt_net_buffer_length; + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + if (opt_delayed) + opt_lock=0; /* Can't have lock with delayed */ + if (!path && (enclosed || opt_enclosed || escaped || lines_terminated || + fields_terminated)) + { + fprintf(stderr, + "%s: You must use option --tab with --fields-...\n", my_progname_short); + return(EX_USAGE); + } + if (!path && opt_header) + { + fprintf(stderr, + "%s: You must use option --tab with --header\n", my_progname_short); + return(EX_USAGE); + } + + /* We don't delete master logs if slave data option */ + if (opt_slave_data) + { + opt_lock_all_tables= !opt_single_transaction; + opt_master_data= 0; + opt_delete_master_logs= 0; + } + + /* Ensure consistency of the set of binlog & locking options */ + if (opt_delete_master_logs && !opt_master_data) + opt_master_data= MYSQL_OPT_MASTER_DATA_COMMENTED_SQL; + if (opt_single_transaction && opt_lock_all_tables) + { + fprintf(stderr, "%s: You can't use --single-transaction and " + "--lock-all-tables at the same time.\n", my_progname_short); + return(EX_USAGE); + } + if (opt_master_data) + { + opt_lock_all_tables= !opt_single_transaction; + opt_slave_data= 0; + } + if (opt_single_transaction || opt_lock_all_tables) + lock_tables= 0; + if (enclosed && opt_enclosed) + { + fprintf(stderr, "%s: You can't use ..enclosed.. and ..optionally-enclosed.. at the same time.\n", my_progname_short); + return(EX_USAGE); + } + if ((opt_databases || opt_alldbs) && path) + { + fprintf(stderr, + "%s: --databases or --all-databases can't be used with --tab.\n", + my_progname_short); + return(EX_USAGE); + } + if (ignore_database.records && !opt_alldbs) + { + fprintf(stderr, + "%s: --ignore-database can only be used together with --all-databases.\n", + my_progname_short); + return(EX_USAGE); + } + if (opt_xml && path) + { + fprintf(stderr, "%s: --xml can't be used with --tab.\n", my_progname_short); + return(EX_USAGE); + } + if (opt_xml && opt_dump_history) + { + fprintf(stderr, "%s: --xml can't be used with --dump-history.\n", + my_progname_short); + return(EX_USAGE); + } + if (opt_replace_into && opt_dump_history) + { + fprintf(stderr, "%s: --dump-history can't be used with --replace.\n", + my_progname_short); + return(EX_USAGE); + } + if (opt_asof_timestamp && opt_dump_history) + { + fprintf(stderr, "%s: --dump-history can't be used with --as-of.\n", + my_progname_short); + return(EX_USAGE); + } + if (opt_asof_timestamp && strchr(opt_asof_timestamp, '\'')) + { + fprintf(stderr, "%s: Incorrect DATETIME value: '%s'\n", + my_progname_short, opt_asof_timestamp); + return(EX_USAGE); + } + + if (strcmp(default_charset, MYSQL_AUTODETECT_CHARSET_NAME) && + !(charset_info= get_charset_by_csname(default_charset, MY_CS_PRIMARY, + MYF(MY_UTF8_IS_UTF8MB3 | MY_WME)))) + exit(1); + if (opt_order_by_size && (*argc > 1 && !opt_databases)) + { + fprintf(stderr, "%s: --order-by-size can't be used when dumping selected tables\n", + my_progname_short); + return EX_USAGE; + } + if ((*argc < 1 && (!opt_alldbs && !opt_system)) || (*argc > 0 && opt_alldbs)) + { + short_usage(stderr); + return EX_USAGE; + } + if (tty_password) + opt_password=my_get_tty_password(NullS); + return(0); +} /* get_options */ + + +/* +** DB_error -- prints mysql error message and exits the program. +*/ +static void DB_error(MYSQL *mysql_arg, const char *when) +{ + DBUG_ENTER("DB_error"); + maybe_die(EX_MYSQLERR, "Got error: %d: \"%s\" %s", + mysql_errno(mysql_arg), mysql_error(mysql_arg), when); + DBUG_VOID_RETURN; +} + + + +/* + Prints out an error message and kills the process. + + SYNOPSIS + die() + error_num - process return value + fmt_reason - a format string for use by my_vsnprintf. + ... - variable arguments for above fmt_reason string + + DESCRIPTION + This call prints out the formatted error message to stderr and then + terminates the process. +*/ +static void die(int error_num, const char* fmt_reason, ...) +{ + char buffer[1000]; + va_list args; + va_start(args,fmt_reason); + my_vsnprintf(buffer, sizeof(buffer), fmt_reason, args); + va_end(args); + + fprintf(stderr, "%s: %s\n", my_progname_short, buffer); + fflush(stderr); + + ignore_errors= 0; /* force the exit */ + maybe_exit(error_num); +} + + +/* + Prints out an error message and maybe kills the process. + + SYNOPSIS + maybe_die() + error_num - process return value + fmt_reason - a format string for use by my_vsnprintf. + ... - variable arguments for above fmt_reason string + + DESCRIPTION + This call prints out the formatted error message to stderr and then + terminates the process, unless the --force command line option is used. + + This call should be used for non-fatal errors (such as database + errors) that the code may still be able to continue to the next unit + of work. + +*/ +static void maybe_die(int error_num, const char* fmt_reason, ...) +{ + char buffer[1000]; + va_list args; + va_start(args,fmt_reason); + my_vsnprintf(buffer, sizeof(buffer), fmt_reason, args); + va_end(args); + + fprintf(stderr, "%s: %s\n", my_progname_short, buffer); + fflush(stderr); + + maybe_exit(error_num); +} + + + +/* + Sends a query to server, optionally reads result, prints error message if + some. + + SYNOPSIS + mysql_query_with_error_report() + mysql_con connection to use + res if non zero, result will be put there with + mysql_store_result() + query query to send to server + + RETURN VALUES + 0 query sending and (if res!=0) result reading went ok + 1 error +*/ + +static int mysql_query_with_error_report(MYSQL *mysql_con, MYSQL_RES **res, + const char *query) +{ + if (mysql_query(mysql_con, query) || + (res && !((*res)= mysql_store_result(mysql_con)))) + { + maybe_die(EX_MYSQLERR, "Couldn't execute '%s': %s (%d)", + query, mysql_error(mysql_con), mysql_errno(mysql_con)); + return 1; + } + return 0; +} + + +static int fetch_db_collation(const char *db_name, + char *db_cl_name, + int db_cl_size) +{ + my_bool err_status= FALSE; + MYSQL_RES *db_cl_res; + MYSQL_ROW db_cl_row; + if (mysql_select_db(mysql, db_name)) + { + DB_error(mysql, "when selecting the database"); + return 1; /* If --force */ + } + + if (mysql_query_with_error_report(mysql, &db_cl_res, + "select @@collation_database")) + return 1; + + do + { + if (mysql_num_rows(db_cl_res) != 1) + { + err_status= TRUE; + break; + } + + if (!(db_cl_row= mysql_fetch_row(db_cl_res))) + { + err_status= TRUE; + break; + } + + strncpy(db_cl_name, db_cl_row[0], db_cl_size-1); + db_cl_name[db_cl_size - 1]= 0; + + } while (FALSE); + + mysql_free_result(db_cl_res); + + return err_status ? 1 : 0; +} + + +/* + Check if server supports non-blocking binlog position using the + binlog_snapshot_file and binlog_snapshot_position status variables. If it + does, also return the position obtained if output pointers are non-NULL. + Returns 1 if position available, 0 if not. +*/ +static int +check_consistent_binlog_pos(char *binlog_pos_file, char *binlog_pos_offset) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int found; + + if (mysql_query_with_error_report(mysql, &res, + "SHOW STATUS LIKE 'binlog_snapshot_%'")) + return 0; + + found= 0; + while ((row= mysql_fetch_row(res))) + { + if (0 == strcmp(row[0], "Binlog_snapshot_file")) + { + if (binlog_pos_file) + strmake(binlog_pos_file, row[1], FN_REFLEN-1); + found++; + } + else if (0 == strcmp(row[0], "Binlog_snapshot_position")) + { + if (binlog_pos_offset) + strmake(binlog_pos_offset, row[1], LONGLONG_LEN); + found++; + } + } + mysql_free_result(res); + + return (found == 2); +} + + +/* + Get the GTID position corresponding to a given old-style binlog position. + This uses BINLOG_GTID_POS(). The advantage is that the GTID position can + be obtained completely non-blocking in this way (without the need for + FLUSH TABLES WITH READ LOCK), as the old-style position can be obtained + with START TRANSACTION WITH CONSISTENT SNAPSHOT. + + Returns 0 if ok, non-zero if error. +*/ +static int +get_binlog_gtid_pos(char *binlog_pos_file, char *binlog_pos_offset, + char *out_gtid_pos) +{ + DYNAMIC_STRING query; + MYSQL_RES *res; + MYSQL_ROW row; + int err; + char file_buf[FN_REFLEN*2+1], offset_buf[LONGLONG_LEN*2+1]; + size_t len_pos_file= strlen(binlog_pos_file); + size_t len_pos_offset= strlen(binlog_pos_offset); + + if (len_pos_file >= FN_REFLEN || len_pos_offset > LONGLONG_LEN) + return 0; + mysql_real_escape_string(mysql, file_buf, binlog_pos_file, (ulong)len_pos_file); + mysql_real_escape_string(mysql, offset_buf, binlog_pos_offset, (ulong)len_pos_offset); + init_dynamic_string_checked(&query, "SELECT BINLOG_GTID_POS('", 256, 1024); + dynstr_append_checked(&query, file_buf); + dynstr_append_checked(&query, "', '"); + dynstr_append_checked(&query, offset_buf); + dynstr_append_checked(&query, "')"); + + err= mysql_query_with_error_report(mysql, &res, query.str); + dynstr_free(&query); + if (err) + return err; + + err= 1; + if ((row= mysql_fetch_row(res))) + { + strmake(out_gtid_pos, row[0], MAX_GTID_LENGTH-1); + err= 0; + } + mysql_free_result(res); + + return err; +} + + +/* + Get the GTID position on a master or slave. + The parameter MASTER is non-zero to get the position on a master + (@@gtid_binlog_pos) or zero for a slave (@@gtid_slave_pos). + + This uses the @@gtid_binlog_pos or @@gtid_slave_pos, so requires FLUSH TABLES + WITH READ LOCK or similar to be consistent. + + Returns 0 if ok, non-zero for error. +*/ +static int +get_gtid_pos(char *out_gtid_pos, int master) +{ + MYSQL_RES *res; + MYSQL_ROW row; + int found; + + if (mysql_query_with_error_report(mysql, &res, + (master ? + "SELECT @@GLOBAL.gtid_binlog_pos" : + "SELECT @@GLOBAL.gtid_slave_pos"))) + return 1; + + found= 0; + if ((row= mysql_fetch_row(res))) + { + strmake(out_gtid_pos, row[0], MAX_GTID_LENGTH-1); + found++; + } + mysql_free_result(res); + + return (found != 1); +} + + +static char *my_case_str(const char *str, + size_t str_len, + const char *token, + uint token_len) +{ + my_match_t match; + + uint status= my_ci_instr(&my_charset_latin1, + str, str_len, + token, token_len, + &match, 1); + + return status ? (char *) str + match.end : NULL; +} + + +static int switch_db_collation(FILE *sql_file, + const char *db_name, + const char *delimiter, + const char *current_db_cl_name, + const char *required_db_cl_name, + int *db_cl_altered) +{ + if (strcmp(current_db_cl_name, required_db_cl_name) != 0) + { + char quoted_db_buf[NAME_LEN * 2 + 3]; + char *quoted_db_name= quote_name(db_name, quoted_db_buf, FALSE); + + CHARSET_INFO *db_cl= get_charset_by_name(required_db_cl_name, MYF(MY_UTF8_IS_UTF8MB3)); + + if (!db_cl) + return 1; + + fprintf(sql_file, + "ALTER DATABASE %s CHARACTER SET %s COLLATE %s %s\n", + (const char *) quoted_db_name, + (const char *) db_cl->cs_name.str, + (const char *) db_cl->coll_name.str, + (const char *) delimiter); + + *db_cl_altered= 1; + + return 0; + } + + *db_cl_altered= 0; + + return 0; +} + + +static int restore_db_collation(FILE *sql_file, + const char *db_name, + const char *delimiter, + const char *db_cl_name) +{ + char quoted_db_buf[NAME_LEN * 2 + 3]; + char *quoted_db_name= quote_name(db_name, quoted_db_buf, FALSE); + + CHARSET_INFO *db_cl= get_charset_by_name(db_cl_name, MYF(MY_UTF8_IS_UTF8MB3)); + + if (!db_cl) + return 1; + + fprintf(sql_file, + "ALTER DATABASE %s CHARACTER SET %s COLLATE %s %s\n", + (const char *) quoted_db_name, + (const char *) db_cl->cs_name.str, + (const char *) db_cl->coll_name.str, + (const char *) delimiter); + + return 0; +} + + +static void switch_cs_variables(FILE *sql_file, + const char *delimiter, + const char *character_set_client, + const char *character_set_results, + const char *collation_connection) +{ + fprintf(sql_file, + "/*!50003 SET @saved_cs_client = @@character_set_client */ %s\n" + "/*!50003 SET @saved_cs_results = @@character_set_results */ %s\n" + "/*!50003 SET @saved_col_connection = @@collation_connection */ %s\n" + "/*!50003 SET character_set_client = %s */ %s\n" + "/*!50003 SET character_set_results = %s */ %s\n" + "/*!50003 SET collation_connection = %s */ %s\n", + (const char *) delimiter, + (const char *) delimiter, + (const char *) delimiter, + + (const char *) character_set_client, + (const char *) delimiter, + + (const char *) character_set_results, + (const char *) delimiter, + + (const char *) collation_connection, + (const char *) delimiter); +} + + +static void restore_cs_variables(FILE *sql_file, + const char *delimiter) +{ + fprintf(sql_file, + "/*!50003 SET character_set_client = @saved_cs_client */ %s\n" + "/*!50003 SET character_set_results = @saved_cs_results */ %s\n" + "/*!50003 SET collation_connection = @saved_col_connection */ %s\n", + (const char *) delimiter, + (const char *) delimiter, + (const char *) delimiter); +} + + +static void switch_sql_mode(FILE *sql_file, + const char *delimiter, + const char *sql_mode) +{ + fprintf(sql_file, + "/*!50003 SET @saved_sql_mode = @@sql_mode */ %s\n" + "/*!50003 SET sql_mode = '%s' */ %s\n", + (const char *) delimiter, + + (const char *) sql_mode, + (const char *) delimiter); +} + + +static void restore_sql_mode(FILE *sql_file, + const char *delimiter) +{ + fprintf(sql_file, + "/*!50003 SET sql_mode = @saved_sql_mode */ %s\n", + (const char *) delimiter); +} + + +static void switch_time_zone(FILE *sql_file, + const char *delimiter, + const char *time_zone) +{ + fprintf(sql_file, + "/*!50003 SET @saved_time_zone = @@time_zone */ %s\n" + "/*!50003 SET time_zone = '%s' */ %s\n", + (const char *) delimiter, + + (const char *) time_zone, + (const char *) delimiter); +} + + +static void restore_time_zone(FILE *sql_file, + const char *delimiter) +{ + fprintf(sql_file, + "/*!50003 SET time_zone = @saved_time_zone */ %s\n", + (const char *) delimiter); +} + + +/** + Switch charset for results to some specified charset. If the server does not + support character_set_results variable, nothing can be done here. As for + whether something should be done here, future new callers of this function + should be aware that the server lacking the facility of switching charsets is + treated as success. + + @note If the server lacks support, then nothing is changed and no error + condition is returned. + + @returns whether there was an error or not +*/ +static int switch_character_set_results(MYSQL *mysql, const char *cs_name) +{ + char query_buffer[QUERY_LENGTH]; + size_t query_length; + + if (!strcmp(cs_name, MYSQL_AUTODETECT_CHARSET_NAME)) + cs_name= (char *)my_default_csname(); + + /* Server lacks facility. This is not an error, by arbitrary decision . */ + if (!server_supports_switching_charsets) + return FALSE; + + query_length= my_snprintf(query_buffer, + sizeof (query_buffer), + "SET SESSION character_set_results = '%s'", + (const char *) cs_name); + + return mysql_real_query(mysql, query_buffer, (ulong)query_length); +} + +/** + Rewrite statement, enclosing DEFINER clause in version-specific comment. + + This function parses any CREATE statement and encloses DEFINER-clause in + version-specific comment: + input query: CREATE DEFINER=a@b FUNCTION ... + rewritten query: CREATE * / / *!50020 DEFINER=a@b * / / *!50003 FUNCTION ... + + @note This function will go away when WL#3995 is implemented. + + @param[in] stmt_str CREATE statement string. + @param[in] stmt_length Length of the stmt_str. + @param[in] definer_version_str Minimal MySQL version number when + DEFINER clause is supported in the + given statement. + @param[in] definer_version_length Length of definer_version_str. + @param[in] stmt_version_str Minimal MySQL version number when the + given statement is supported. + @param[in] stmt_version_length Length of stmt_version_str. + @param[in] keyword_str Keyword to look for after CREATE. + @param[in] keyword_length Length of keyword_str. + + @return pointer to the new allocated query string. +*/ + +static char *cover_definer_clause(const char *stmt_str, + size_t stmt_length, + const char *definer_version_str, + uint definer_version_length, + const char *stmt_version_str, + uint stmt_version_length, + const char *keyword_str, + uint keyword_length) +{ + char *definer_begin= my_case_str(stmt_str, stmt_length, + C_STRING_WITH_LEN(" DEFINER")); + char *definer_end= NULL; + + char *query_str= NULL; + char *query_ptr; + + if (!definer_begin) + return NULL; + + definer_end= my_case_str(definer_begin, strlen(definer_begin), + keyword_str, keyword_length); + + if (!definer_end) + return NULL; + + /* + Allocate memory for new query string: original string + from SHOW statement and version-specific comments. + */ + query_str= alloc_query_str(stmt_length + 23); + + query_ptr= strnmov(query_str, stmt_str, definer_begin - stmt_str); + query_ptr= strnmov(query_ptr, C_STRING_WITH_LEN("*/ /*!")); + query_ptr= strnmov(query_ptr, definer_version_str, definer_version_length); + query_ptr= strnmov(query_ptr, definer_begin, definer_end - definer_begin); + query_ptr= strnmov(query_ptr, C_STRING_WITH_LEN("*/ /*!")); + query_ptr= strnmov(query_ptr, stmt_version_str, stmt_version_length); + query_ptr= strxmov(query_ptr, definer_end, NullS); + + return query_str; +} + +/* + Open a new .sql file to dump the table or view into + + SYNOPSIS + open_sql_file_for_table + name name of the table or view + flags flags (as per "man 2 open") + + RETURN VALUES + 0 Failed to open file + > 0 Handle of the open file +*/ +static FILE* open_sql_file_for_table(const char* table, int flags) +{ + FILE* res; + char filename[FN_REFLEN], tmp_path[FN_REFLEN]; + convert_dirname(tmp_path,path,NullS); + res= my_fopen(fn_format(filename, table, tmp_path, ".sql", 4), + flags, MYF(MY_WME)); + return res; +} + + +static void free_resources() +{ + if (md_result_file) + { + if (md_result_file != stdout) + my_fclose(md_result_file, MYF(0)); + else + fflush(md_result_file); + } + if (get_table_name_result) + mysql_free_result(get_table_name_result); + if (routine_res) + mysql_free_result(routine_res); + if (routine_list_res) + mysql_free_result(routine_list_res); + if (mysql) + { + mysql_close(mysql); + mysql= 0; + } + my_free(order_by); + my_free(opt_password); + my_free(current_host); + free_root(&glob_root, MYF(0)); + if (my_hash_inited(&ignore_database)) + my_hash_free(&ignore_database); + if (my_hash_inited(&ignore_table)) + my_hash_free(&ignore_table); + if (my_hash_inited(&ignore_data)) + my_hash_free(&ignore_data); + dynstr_free(&extended_row); + dynstr_free(&dynamic_where); + dynstr_free(&insert_pat); + dynstr_free(&select_field_names); + dynstr_free(&select_field_names_for_header); + if (defaults_argv) + free_defaults(defaults_argv); + mysql_library_end(); + my_end(my_end_arg); +} + + +static void maybe_exit(int error) +{ + if (!first_error) + first_error= error; + if (ignore_errors) + return; + ignore_errors= 1; /* don't want to recurse, if something fails below */ + if (opt_slave_data) + do_start_slave_sql(mysql); + free_resources(); + exit(error); +} + + +/* + db_connect -- connects to the host and selects DB. +*/ + +static int connect_to_db(char *host, char *user,char *passwd) +{ + char buff[20+FN_REFLEN]; + my_bool reconnect; + DBUG_ENTER("connect_to_db"); + + verbose_msg("-- Connecting to %s...\n", host ? host : "localhost"); + mysql_init(&mysql_connection); + if (opt_compress) + mysql_options(&mysql_connection,MYSQL_OPT_COMPRESS,NullS); +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(&mysql_connection, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(&mysql_connection, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(&mysql_connection, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(&mysql_connection,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(&mysql_connection,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + mysql_options(&mysql_connection, MYSQL_SET_CHARSET_NAME, default_charset); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql_connection, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql_connection, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(&mysql_connection, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqldump"); + mysql= &mysql_connection; /* So we can mysql_close() it properly */ + if (!mysql_real_connect(&mysql_connection,host,user,passwd, + NULL,opt_mysql_port,opt_mysql_unix_port, 0)) + { + DB_error(&mysql_connection, "when trying to connect"); + DBUG_RETURN(1); + } + if ((mysql_get_server_version(&mysql_connection) < 40100) || + (opt_compatible_mode & 3)) + { + /* Don't dump SET NAMES with a pre-4.1 server (bug#7997). */ + opt_set_charset= 0; + + /* Don't switch charsets for 4.1 and earlier. (bug#34192). */ + server_supports_switching_charsets= FALSE; + } + /* + As we're going to set SQL_MODE, it would be lost on reconnect, so we + cannot reconnect. + */ + reconnect= 0; + mysql_options(&mysql_connection, MYSQL_OPT_RECONNECT, &reconnect); + my_snprintf(buff, sizeof(buff), "/*!40100 SET @@SQL_MODE='%s' */", + compatible_mode_normal_str); + if (mysql_query_with_error_report(mysql, 0, buff)) + DBUG_RETURN(1); + /* + set time_zone to UTC to allow dumping date types between servers with + different time zone settings + */ + if (opt_tz_utc) + { + my_snprintf(buff, sizeof(buff), "/*!40103 SET TIME_ZONE='+00:00' */"); + if (mysql_query_with_error_report(mysql, 0, buff)) + DBUG_RETURN(1); + } + DBUG_RETURN(0); +} /* connect_to_db */ + + +/* +** dbDisconnect -- disconnects from the host. +*/ +static void dbDisconnect(char *host) +{ + verbose_msg("-- Disconnecting from %s...\n", host ? host : "localhost"); + mysql_close(mysql); + mysql= 0; +} /* dbDisconnect */ + + +static void unescape(FILE *file,char *pos, size_t length) +{ + char *tmp; + DBUG_ENTER("unescape"); + if (!(tmp=(char*) my_malloc(PSI_NOT_INSTRUMENTED, length*2+1, MYF(MY_WME)))) + die(EX_MYSQLERR, "Couldn't allocate memory"); + + mysql_real_escape_string(&mysql_connection, tmp, pos, (ulong)length); + fputc('\'', file); + fputs(tmp, file); + fputc('\'', file); + check_io(file); + my_free(tmp); + DBUG_VOID_RETURN; +} /* unescape */ + + +static my_bool test_if_special_chars(const char *str) +{ +#if MYSQL_VERSION_ID >= 32300 + for ( ; *str ; str++) + if (!my_isvar(charset_info,*str) && *str != '$') + return 1; +#endif + return 0; +} /* test_if_special_chars */ + + +/* + quote(name, buff, force, quote_c) + + Quotes a string, if it requires quoting. To force quoting regardless + of the characters within the string, the force flag can be set to true. + + Args + + name Unquoted string containing that which will be quoted + buff The buffer that contains the quoted value, also returned + force Flag to make it ignore 'test_if_special_chars' + + Returns + A pointer to the quoted string, or the original string if nothing has + changed. + +*/ +static char *quote_name(const char *name, char *buff, my_bool force) +{ + char *to= buff; + char qtype= (opt_compatible_mode & MASK_ANSI_QUOTES) ? '\"' : '`'; + + if (!force && !opt_quoted && !test_if_special_chars(name)) + return (char*) name; + *to++= qtype; + while (*name) + { + if (*name == qtype) + *to++= qtype; + *to++= *name++; + } + to[0]= qtype; + to[1]= 0; + return buff; +} /* quote_name */ + + +/* + Quote a table name so it can be used in "SHOW TABLES LIKE " + + SYNOPSIS + quote_for_like() + name name of the table + buff quoted name of the table + + DESCRIPTION + Quote \, _, ' and % characters + + Note: Because MySQL uses the C escape syntax in strings + (for example, '\n' to represent newline), you must double + any '\' that you use in your LIKE strings. For example, to + search for '\n', specify it as '\\n'. To search for '\', specify + it as '\\\\' (the backslashes are stripped once by the parser + and another time when the pattern match is done, leaving a + single backslash to be matched). + + Example: "t\1" => "t\\\\1" + +*/ +static char *quote_for_like(const char *name, char *buff) +{ + char *to= buff; + *to++= '\''; + while (*name) + { + if (*name == '\\') + { + *to++='\\'; + *to++='\\'; + *to++='\\'; + } + else if (*name == '\'' || *name == '_' || *name == '%') + *to++= '\\'; + *to++= *name++; + } + to[0]= '\''; + to[1]= 0; + return buff; +} + +static char *quote_for_equal(const char *name, char *buff) +{ + char *to= buff; + *to++= '\''; + while (*name) + { + if (*name == '\\') + { + *to++='\\'; + } + if (*name == '\'') + *to++= '\\'; + *to++= *name++; + } + to[0]= '\''; + to[1]= 0; + return buff; + +} + + +/** + Quote and print a string. + + @param xml_file - Output file. + @param str - String to print. + @param len - Its length. + @param is_attribute_name - A check for attribute name or value. + + @description + Quote '<' '>' '&' '\"' chars and print a string to the xml_file. +*/ + +static void print_quoted_xml(FILE *xml_file, const char *str, size_t len, + my_bool is_attribute_name) +{ + const char *end; + + for (end= str + len; str != end; str++) + { + switch (*str) { + case '<': + fputs("<", xml_file); + break; + case '>': + fputs(">", xml_file); + break; + case '&': + fputs("&", xml_file); + break; + case '\"': + fputs(""", xml_file); + break; + case ' ': + /* Attribute names cannot contain spaces. */ + if (is_attribute_name) + { + fputs("_", xml_file); + break; + } + /* fall through */ + default: + fputc(*str, xml_file); + break; + } + } + check_io(xml_file); +} + + +/* + Print xml tag. Optionally add attribute(s). + + SYNOPSIS + print_xml_tag(xml_file, sbeg, send, tag_name, first_attribute_name, + ..., attribute_name_n, attribute_value_n, NullS) + xml_file - output file + sbeg - line beginning + line_end - line ending + tag_name - XML tag name. + first_attribute_name - tag and first attribute + first_attribute_value - (Implied) value of first attribute + attribute_name_n - attribute n + attribute_value_n - value of attribute n + + DESCRIPTION + Print XML tag with any number of attribute="value" pairs to the xml_file. + + Format is: + sbegsend + NOTE + Additional arguments must be present in attribute/value pairs. + The last argument should be the null character pointer. + All attribute_value arguments MUST be NULL terminated strings. + All attribute_value arguments will be quoted before output. +*/ + +static void print_xml_tag(FILE * xml_file, const char* sbeg, + const char* line_end, + const char* tag_name, + const char* first_attribute_name, ...) +{ + va_list arg_list; + const char *attribute_name, *attribute_value; + + fputs(sbeg, xml_file); + fputc('<', xml_file); + fputs(tag_name, xml_file); + + va_start(arg_list, first_attribute_name); + attribute_name= first_attribute_name; + while (attribute_name != NullS) + { + attribute_value= va_arg(arg_list, char *); + DBUG_ASSERT(attribute_value != NullS); + + fputc(' ', xml_file); + fputs(attribute_name, xml_file); + fputc('\"', xml_file); + + print_quoted_xml(xml_file, attribute_value, strlen(attribute_value), 0); + fputc('\"', xml_file); + + attribute_name= va_arg(arg_list, char *); + } + va_end(arg_list); + + fputc('>', xml_file); + fputs(line_end, xml_file); + check_io(xml_file); +} + + +/* + Print xml tag with for a field that is null + + SYNOPSIS + print_xml_null_tag() + xml_file - output file + sbeg - line beginning + stag_atr - tag and attribute + sval - value of attribute + line_end - line ending + + DESCRIPTION + Print tag with one attribute to the xml_file. Format is: + + NOTE + sval MUST be a NULL terminated string. + sval string will be quoted before output. +*/ + +static void print_xml_null_tag(FILE * xml_file, const char* sbeg, + const char* stag_atr, const char* sval, + const char* line_end) +{ + fputs(sbeg, xml_file); + fputs("<", xml_file); + fputs(stag_atr, xml_file); + fputs("\"", xml_file); + print_quoted_xml(xml_file, sval, strlen(sval), 0); + fputs("\" xsi:nil=\"true\" />", xml_file); + fputs(line_end, xml_file); + check_io(xml_file); +} + + +/** + Print xml CDATA section. + + @param xml_file - output file + @param str - string to print + @param len - length of the string + + @note + This function also takes care of the presence of '[[>' + string in the str. If found, the CDATA section is broken + into two CDATA sections, and ]]. +*/ + +static void print_xml_cdata(FILE *xml_file, const char *str, ulong len) +{ + const char *end; + + fputs("')) + { + fputs("]]]]>", xml_file); + str += 2; + continue; + } + /* fall through */ + default: + fputc(*str, xml_file); + break; + } + } + fputs("\n]]>\n", xml_file); + check_io(xml_file); +} + + +/* + Print xml tag with many attributes. + + SYNOPSIS + print_xml_row() + xml_file - output file + row_name - xml tag name + tableRes - query result + row - result row + str_create - create statement header string + + DESCRIPTION + Print tag with many attribute to the xml_file. Format is: + \t\t + NOTE + All attributes and values will be quoted before output. +*/ + +static void print_xml_row(FILE *xml_file, const char *row_name, + MYSQL_RES *tableRes, MYSQL_ROW *row, + const char *str_create) +{ + uint i; + my_bool body_found __attribute__((unused)) = 0; + char *create_stmt_ptr= NULL; + ulong create_stmt_len= 0; + MYSQL_FIELD *field; + ulong *lengths= mysql_fetch_lengths(tableRes); + + fprintf(xml_file, "\t\t<%s", row_name); + check_io(xml_file); + mysql_field_seek(tableRes, 0); + for (i= 0; (field= mysql_fetch_field(tableRes)); i++) + { + if ((*row)[i]) + { + /* For 'create' statements, dump using CDATA. */ + if ((str_create) && (strcmp(str_create, field->name) == 0)) + { + create_stmt_ptr= (*row)[i]; + create_stmt_len= lengths[i]; +#ifdef DBUG_ASSERT_EXISTS + body_found= 1; +#endif + } + else + { + fputc(' ', xml_file); + print_quoted_xml(xml_file, field->name, field->name_length, 1); + fputs("=\"", xml_file); + if (opt_copy_s3_tables && + !strcmp(field->name, "Engine") && + !strcmp((*row)[i], "S3")) + print_quoted_xml(xml_file, "Aria", sizeof("Aria") - 1, 0); + else + print_quoted_xml(xml_file, (*row)[i], lengths[i], 0); + fputc('"', xml_file); + check_io(xml_file); + } + } + } + + if (create_stmt_len) + { + DBUG_ASSERT(body_found); + fputs(">\n", xml_file); + print_xml_cdata(xml_file, create_stmt_ptr, create_stmt_len); + fprintf(xml_file, "\t\t\n", row_name); + } + else + fputs(" />\n", xml_file); + + check_io(xml_file); +} + + +/** + Print xml comments. + + @param xml_file - output file + @param len - length of comment message + @param comment_string - comment message + + @description + Print the comment message in the format: + "\n" + + @note + Any occurrence of continuous hyphens will be + squeezed to a single hyphen. +*/ + +static void print_xml_comment(FILE *xml_file, size_t len, + const char *comment_string) +{ + const char* end; + + fputs("\n", xml_file); + check_io(xml_file); +} + + + +/* A common printing function for xml and non-xml modes. */ + +static void print_comment(FILE *sql_file, my_bool is_error, const char *format, + ...) +{ + static char comment_buff[COMMENT_LENGTH]; + va_list args; + + /* If its an error message, print it ignoring opt_comments. */ + if (!is_error && !opt_comments) + return; + + va_start(args, format); + my_vsnprintf(comment_buff, COMMENT_LENGTH, format, args); + va_end(args); + + if (!opt_xml) + { + fputs(comment_buff, sql_file); + check_io(sql_file); + return; + } + + print_xml_comment(sql_file, strlen(comment_buff), comment_buff); +} + +/* + create_delimiter + Generate a new (null-terminated) string that does not exist in query + and is therefore suitable for use as a query delimiter. Store this + delimiter in delimiter_buff . + + This is quite simple in that it doesn't even try to parse statements as an + interpreter would. It merely returns a string that is not in the query, which + is much more than adequate for constructing a delimiter. + + RETURN + ptr to the delimiter on Success + NULL on Failure +*/ +static char *create_delimiter(char *query, char *delimiter_buff, + int delimiter_max_size) +{ + int proposed_length; + char *presence; + + delimiter_buff[0]= ';'; /* start with one semicolon, and */ + + for (proposed_length= 2; proposed_length < delimiter_max_size; + delimiter_max_size++) { + + delimiter_buff[proposed_length-1]= ';'; /* add semicolons, until */ + delimiter_buff[proposed_length]= '\0'; + + presence = strstr(query, delimiter_buff); + if (presence == NULL) { /* the proposed delimiter is not in the query. */ + return delimiter_buff; + } + + } + return NULL; /* but if we run out of space, return nothing at all. */ +} + + +/* + dump_events_for_db + -- retrieves list of events for a given db, and prints out + the CREATE EVENT statement into the output (the dump). + + RETURN + 0 Success + 1 Error +*/ +static uint dump_events_for_db(char *db) +{ + char query_buff[QUERY_LENGTH]; + char db_name_buff[NAME_LEN*2+3], name_buff[NAME_LEN*2+3]; + char *event_name; + char delimiter[QUERY_LENGTH]; + FILE *sql_file= md_result_file; + MYSQL_RES *event_res, *event_list_res; + MYSQL_ROW row, event_list_row; + + char db_cl_name[MY_CS_COLLATION_NAME_SIZE]; + int db_cl_altered= FALSE; + + DBUG_ENTER("dump_events_for_db"); + DBUG_PRINT("enter", ("db: '%s'", db)); + + mysql_real_escape_string(mysql, db_name_buff, db, (ulong)strlen(db)); + + /* nice comments */ + print_comment(sql_file, 0, + "\n--\n-- Dumping events for database '%s'\n--\n", + fix_for_comment(db)); + + /* + not using "mysql_query_with_error_report" because we may have not + enough privileges to lock mysql.events. + */ + if (lock_tables) + mysql_query(mysql, "LOCK TABLES mysql.event READ"); + + if (mysql_query_with_error_report(mysql, &event_list_res, "show events")) + DBUG_RETURN(0); + + safe_strcpy(delimiter, sizeof(delimiter), ";"); + if (mysql_num_rows(event_list_res) > 0) + { + if (opt_xml) + fputs("\t\n", sql_file); + else + { + fprintf(sql_file, "/*!50106 SET @save_time_zone= @@TIME_ZONE */ ;\n"); + + /* Get database collation. */ + + if (fetch_db_collation(db_name_buff, db_cl_name, sizeof (db_cl_name))) + { + mysql_free_result(event_list_res); + DBUG_RETURN(1); + } + } + + if (switch_character_set_results(mysql, "binary")) + DBUG_RETURN(1); + + while ((event_list_row= mysql_fetch_row(event_list_res)) != NULL) + { + event_name= quote_name(event_list_row[1], name_buff, 0); + DBUG_PRINT("info", ("retrieving CREATE EVENT for %s", name_buff)); + my_snprintf(query_buff, sizeof(query_buff), "SHOW CREATE EVENT %s", + event_name); + + if (mysql_query_with_error_report(mysql, &event_res, query_buff)) + DBUG_RETURN(1); + + while ((row= mysql_fetch_row(event_res)) != NULL) + { + if (opt_xml) + { + print_xml_row(sql_file, "event", event_res, &row, + "Create Event"); + continue; + } + + /* + if the user has EXECUTE privilege he can see event names, but not the + event body! + */ + if (strlen(row[3]) != 0) + { + char *query_str; + + if (opt_drop) + fprintf(sql_file, "/*!50106 DROP EVENT IF EXISTS %s */%s\n", + event_name, delimiter); + + if (create_delimiter(row[3], delimiter, sizeof(delimiter)) == NULL) + { + fprintf(stderr, "%s: Warning: Can't create delimiter for event '%s'\n", + my_progname_short, event_name); + DBUG_RETURN(1); + } + + fprintf(sql_file, "DELIMITER %s\n", delimiter); + + if (mysql_num_fields(event_res) >= 7) + { + if (switch_db_collation(sql_file, db_name_buff, delimiter, + db_cl_name, row[6], &db_cl_altered)) + { + DBUG_RETURN(1); + } + + switch_cs_variables(sql_file, delimiter, + row[4], /* character_set_client */ + row[4], /* character_set_results */ + row[5]); /* collation_connection */ + } + else + { + /* + mysqldump is being run against the server, that does not + provide character set information in SHOW CREATE + statements. + + NOTE: the dump may be incorrect, since character set + information is required in order to restore event properly. + */ + + fprintf(sql_file, + "--\n" + "-- WARNING: old server version. " + "The following dump may be incomplete.\n" + "--\n"); + } + + switch_sql_mode(sql_file, delimiter, row[1]); + + switch_time_zone(sql_file, delimiter, row[2]); + + query_str= cover_definer_clause(row[3], strlen(row[3]), + C_STRING_WITH_LEN("50117"), + C_STRING_WITH_LEN("50106"), + C_STRING_WITH_LEN(" EVENT")); + + fprintf(sql_file, + "/*!50106 %s */ %s\n", + (const char *) (query_str != NULL ? query_str : row[3]), + (const char *) delimiter); + + my_free(query_str); + restore_time_zone(sql_file, delimiter); + restore_sql_mode(sql_file, delimiter); + + if (mysql_num_fields(event_res) >= 7) + { + restore_cs_variables(sql_file, delimiter); + + if (db_cl_altered) + { + if (restore_db_collation(sql_file, db_name_buff, delimiter, + db_cl_name)) + DBUG_RETURN(1); + } + } + } + } /* end of event printing */ + mysql_free_result(event_res); + + } /* end of list of events */ + if (opt_xml) + { + fputs("\t\n", sql_file); + check_io(sql_file); + } + else + { + fprintf(sql_file, "DELIMITER ;\n"); + fprintf(sql_file, "/*!50106 SET TIME_ZONE= @save_time_zone */ ;\n"); + } + + if (switch_character_set_results(mysql, default_charset)) + DBUG_RETURN(1); + } + mysql_free_result(event_list_res); + + if (lock_tables) + (void) mysql_query_with_error_report(mysql, 0, "UNLOCK TABLES"); + DBUG_RETURN(0); +} + + +/* + Print hex value for blob data. + + SYNOPSIS + print_blob_as_hex() + output_file - output file + str - string to print + len - its length + + DESCRIPTION + Print hex value for blob data. +*/ + +static void print_blob_as_hex(FILE *output_file, const char *str, ulong len) +{ + /* sakaik got the idea to to provide blob's in hex notation. */ + const char *ptr= str, *end= ptr + len; + for (; ptr < end ; ptr++) + fprintf(output_file, "%02X", *((uchar *)ptr)); + check_io(output_file); +} + +/* + dump_routines_for_db + -- retrieves list of routines for a given db, and prints out + the CREATE PROCEDURE definition into the output (the dump). + + This function has logic to print the appropriate syntax depending on whether + this is a procedure or functions + + RETURN + 0 Success + 1 Error +*/ + +static uint dump_routines_for_db(char *db) +{ + char query_buff[QUERY_LENGTH]; + const char *routine_type[]= {"FUNCTION", + "PROCEDURE", + "PACKAGE", + "PACKAGE BODY"}; + const char *create_caption_xml[]= {"Create Function", + "Create Procedure", + "Create Package", + "Create Package Body"}; + char db_name_buff[NAME_LEN*2+3], name_buff[NAME_LEN*2+3]; + char *routine_name; + uint i; + FILE *sql_file= md_result_file; + MYSQL_ROW row, routine_list_row; + + char db_cl_name[MY_CS_COLLATION_NAME_SIZE]; + int db_cl_altered= FALSE; + // before 10.3 packages are not supported + uint upper_bound= mysql_get_server_version(mysql) >= 100300 ? + array_elements(routine_type) : 2; + DBUG_ENTER("dump_routines_for_db"); + DBUG_PRINT("enter", ("db: '%s'", db)); + + mysql_real_escape_string(mysql, db_name_buff, db, (ulong)strlen(db)); + + /* nice comments */ + print_comment(sql_file, 0, + "\n--\n-- Dumping routines for database '%s'\n--\n", + fix_for_comment(db)); + + /* + not using "mysql_query_with_error_report" because we may have not + enough privileges to lock mysql.proc. + */ + if (lock_tables) + mysql_query(mysql, "LOCK TABLES mysql.proc READ"); + + /* Get database collation. */ + + if (fetch_db_collation(db, db_cl_name, sizeof (db_cl_name))) + DBUG_RETURN(1); + + if (switch_character_set_results(mysql, "binary")) + DBUG_RETURN(1); + + if (opt_xml) + fputs("\t\n", sql_file); + + /* 0, retrieve and dump functions, 1, procedures, etc. */ + for (i= 0; i < upper_bound; i++) + { + my_snprintf(query_buff, sizeof(query_buff), + "SHOW %s STATUS WHERE Db = '%s'", + routine_type[i], db_name_buff); + + if (mysql_query_with_error_report(mysql, &routine_list_res, query_buff)) + DBUG_RETURN(1); + + if (mysql_num_rows(routine_list_res)) + { + + while ((routine_list_row= mysql_fetch_row(routine_list_res))) + { + routine_name= quote_name(routine_list_row[1], name_buff, 0); + DBUG_PRINT("info", ("retrieving CREATE %s for %s", routine_type[i], + name_buff)); + my_snprintf(query_buff, sizeof(query_buff), "SHOW CREATE %s %s", + routine_type[i], routine_name); + + if (mysql_query_with_error_report(mysql, &routine_res, query_buff)) + continue; + + while ((row= mysql_fetch_row(routine_res))) + { + /* + if the user has EXECUTE privilege he see routine names, but NOT the + routine body of other routines that are not the creator of! + */ + DBUG_PRINT("info",("length of body for %s row[2] '%s' is %zu", + routine_name, row[2] ? row[2] : "(null)", + row[2] ? strlen(row[2]) : 0)); + if (row[2] == NULL) + { + print_comment(sql_file, 1, "\n-- insufficient privileges to %s\n", + query_buff); + print_comment(sql_file, 1, + "-- does %s have permissions on mysql.proc?\n\n", + fix_for_comment(current_user)); + maybe_die(EX_MYSQLERR,"%s has insufficient privileges to %s!", + current_user, query_buff); + } + else if (strlen(row[2])) + { + if (opt_xml) + { + print_xml_row(sql_file, "routine", routine_res, &row, + create_caption_xml[i]); + continue; + } + + switch_sql_mode(sql_file, ";", row[1]); + + if (opt_drop) + fprintf(sql_file, "/*!50003 DROP %s IF EXISTS %s */;\n", + routine_type[i], routine_name); + + if (mysql_num_fields(routine_res) >= 6) + { + if (switch_db_collation(sql_file, db, ";", + db_cl_name, row[5], &db_cl_altered)) + { + mysql_free_result(routine_res); + mysql_free_result(routine_list_res); + routine_res= routine_list_res= 0; + DBUG_RETURN(1); + } + + switch_cs_variables(sql_file, ";", + row[3], /* character_set_client */ + row[3], /* character_set_results */ + row[4]); /* collation_connection */ + } + else + { + /* + mysqldump is being run against the server, that does not + provide character set information in SHOW CREATE + statements. + + NOTE: the dump may be incorrect, since character set + information is required in order to restore stored + procedure/function properly. + */ + + fprintf(sql_file, + "--\n" + "-- WARNING: old server version. " + "The following dump may be incomplete.\n" + "--\n"); + } + + fprintf(sql_file, + "DELIMITER ;;\n" + "%s ;;\n" + "DELIMITER ;\n", + (const char *) row[2]); + + restore_sql_mode(sql_file, ";"); + + if (mysql_num_fields(routine_res) >= 6) + { + restore_cs_variables(sql_file, ";"); + + if (db_cl_altered) + { + if (restore_db_collation(sql_file, db, ";", db_cl_name)) + { + mysql_free_result(routine_res); + mysql_free_result(routine_list_res); + routine_res= routine_list_res= 0; + DBUG_RETURN(1); + } + } + } + + } + } /* end of routine printing */ + mysql_free_result(routine_res); + routine_res= 0; + + } /* end of list of routines */ + } + mysql_free_result(routine_list_res); + routine_list_res= 0; + } /* end of for i (0 .. 1) */ + + if (opt_xml) + { + fputs("\t\n", sql_file); + check_io(sql_file); + } + + if (switch_character_set_results(mysql, default_charset)) + DBUG_RETURN(1); + + if (lock_tables) + (void) mysql_query_with_error_report(mysql, 0, "UNLOCK TABLES"); + DBUG_RETURN(0); +} + +/* general_log or slow_log tables under mysql database */ +static inline my_bool general_log_or_slow_log_tables(const char *db, + const char *table) +{ + return (!my_strcasecmp(charset_info, db, "mysql")) && + (!my_strcasecmp(charset_info, table, "general_log") || + !my_strcasecmp(charset_info, table, "slow_log") || + !my_strcasecmp(charset_info, table, "transaction_registry")); +} +/* + get_sequence_structure-- retrieves sequence structure, prints out corresponding + CREATE statement + ARGS + seq - sequence name + db - db name +*/ + +static void get_sequence_structure(const char *seq, const char *db) +{ + + char table_buff[NAME_LEN*2+3]; + char *result_seq; + FILE *sql_file= md_result_file; + MYSQL_RES *result; + MYSQL_ROW row; + + DBUG_ENTER("get_sequence_structure"); + DBUG_PRINT("enter", ("db: %s sequence: %s", db, seq)); + + verbose_msg("-- Retrieving sequence structure for %s...\n", seq); + + result_seq= quote_name(seq, table_buff, 1); + // Sequences as tables share same flags + if (!opt_no_create_info) + { + char buff[20+FN_REFLEN]; + my_snprintf(buff, sizeof(buff), "SHOW CREATE SEQUENCE %s", result_seq); + if (mysql_query_with_error_report(mysql, &result, buff)) + { + DBUG_VOID_RETURN; + } + + print_comment(sql_file, 0, + "\n--\n-- Sequence structure for %s\n--\n\n", + fix_for_comment(result_seq)); + if (opt_drop) + { + fprintf(sql_file, "DROP SEQUENCE IF EXISTS %s;\n", result_seq); + check_io(sql_file); + } + + row= mysql_fetch_row(result); + fprintf(sql_file, "%s;\n", row[1]); + mysql_free_result(result); + + // Restore next not cached value from sequence + my_snprintf(buff, sizeof(buff), "SELECT next_not_cached_value FROM %s", result_seq); + if (mysql_query_with_error_report(mysql, &result, buff)) + { + DBUG_VOID_RETURN; + } + row= mysql_fetch_row(result); + if (row[0]) + { + fprintf(sql_file, "SELECT SETVAL(%s, %s, 0);\n", result_seq, row[0]); + } + // Sequences will not use inserts, so no need for REPLACE and LOCKS + mysql_free_result(result); + } + DBUG_VOID_RETURN; +} +/* + get_table_structure -- retrieves database structure, prints out corresponding + CREATE statement and fills out insert_pat if the table is the type we will + be dumping. + + ARGS + table - table name + db - db name + table_type - table type, e.g. "MyISAM" or "InnoDB", but also "VIEW" + ignore_flag - what we must particularly ignore - see IGNORE_ defines above + + RETURN + number of fields in table, 0 if error +*/ + +static uint get_table_structure(const char *table, const char *db, char *table_type, + char *ignore_flag, my_bool *versioned) +{ + my_bool init=0, delayed, write_data, complete_insert; + my_ulonglong num_fields; + char *result_table, *opt_quoted_table; + const char *insert_option; + char name_buff[NAME_LEN+3],table_buff[NAME_LEN*2+3]; + char table_buff2[NAME_LEN*2+3], query_buff[QUERY_LENGTH]; + char temp_buff[NAME_LEN*2 + 3], temp_buff2[NAME_LEN*2 + 3]; + FILE *sql_file= md_result_file; + size_t len; + my_bool is_log_table; + MYSQL_RES *result; + MYSQL_ROW row; + const char *s3_engine_ptr; + DYNAMIC_STRING create_table_str; + static const char s3_engine_token[]= " ENGINE=S3 "; + static const char aria_engine_token[]= " ENGINE=Aria "; + DBUG_ENTER("get_table_structure"); + DBUG_PRINT("enter", ("db: %s table: %s", db, table)); + + *ignore_flag= check_if_ignore_table(table, table_type); + + if (!opt_copy_s3_tables && *ignore_flag == IGNORE_S3_TABLE) + DBUG_RETURN(0); + + delayed= opt_delayed; + if (delayed && (*ignore_flag & IGNORE_INSERT_DELAYED)) + { + delayed= 0; + verbose_msg("-- Warning: Unable to use delayed inserts for table '%s' " + "because it's of type %s\n", table, table_type); + } + + complete_insert= 0; + if ((write_data= !(*ignore_flag & IGNORE_DATA))) + { + complete_insert= opt_complete_insert; + if (!insert_pat_inited) + { + insert_pat_inited= 1; + init_dynamic_string_checked(&insert_pat, "", 1024, 1024); + } + else + dynstr_set_checked(&insert_pat, ""); + } + if (!select_field_names_inited) + { + select_field_names_inited= 1; + init_dynamic_string_checked(&select_field_names, "", 1024, 1024); + if (opt_header) + init_dynamic_string_checked(&select_field_names_for_header, "", 1024, 1024); + } + else + { + dynstr_set_checked(&select_field_names, ""); + if (opt_header) + dynstr_set_checked(&select_field_names_for_header, ""); + } + insert_option= ((delayed && opt_ignore) ? " DELAYED IGNORE " : + delayed ? " DELAYED " : opt_ignore ? " IGNORE " : ""); + + verbose_msg("-- Retrieving table structure for table %s...\n", table); + + if (versioned) + { + if (!opt_asof_timestamp && !opt_dump_history) + versioned= NULL; + else + { + my_snprintf(query_buff, sizeof(query_buff), "select 1 from" + " information_schema.tables where table_schema=database()" + " and table_name=%s and table_type='SYSTEM VERSIONED'", + quote_for_equal(table, table_buff)); + if (!mysql_query_with_error_report(mysql, &result, query_buff)) + { + *versioned= result->row_count > 0; + mysql_free_result(result); + } + else + *versioned= 0; + } + } + + len= my_snprintf(query_buff, sizeof(query_buff), + "SET SQL_QUOTE_SHOW_CREATE=%d", opt_quoted || opt_keywords); + if (!create_options) + strmov(query_buff+len, + "/*!40102 ,SQL_MODE=concat(@@sql_mode, _utf8 ',NO_KEY_OPTIONS,NO_TABLE_OPTIONS,NO_FIELD_OPTIONS') */"); + + result_table= quote_name(table, table_buff, 1); + opt_quoted_table= quote_name(table, table_buff2, 0); + + if (opt_order_by_primary) + order_by= primary_key_fields(result_table); + + if (!opt_xml && !mysql_query_with_error_report(mysql, 0, query_buff)) + { + int vers_hidden= opt_dump_history && versioned && *versioned; + /* using SHOW CREATE statement */ + if (!opt_no_create_info) + { + /* Make an sql-file, if path was given iow. option -T was given */ + char buff[20+FN_REFLEN]; + MYSQL_FIELD *field; + + my_snprintf(buff, sizeof(buff), "show create table %s", result_table); + + if (switch_character_set_results(mysql, "binary") || + mysql_query_with_error_report(mysql, &result, buff) || + switch_character_set_results(mysql, default_charset)) + { + my_free(order_by); + order_by= 0; + DBUG_RETURN(0); + } + + if (path) + { + if (!(sql_file= open_sql_file_for_table(table, O_WRONLY))) + { + my_free(order_by); + order_by= 0; + DBUG_RETURN(0); + } + write_header(sql_file, db); + } + + if (strcmp (table_type, "VIEW") == 0) /* view */ + print_comment(sql_file, 0, + "\n--\n-- Temporary table structure for view %s\n--\n\n", + fix_for_comment(result_table)); + else + print_comment(sql_file, 0, + "\n--\n-- Table structure for table %s\n--\n\n", + fix_for_comment(result_table)); + + if (opt_drop) + { + /* + Even if the "table" is a view, we do a DROP TABLE here. The + view-specific code below fills in the DROP VIEW. + We will skip the DROP TABLE for general_log and slow_log, since + those stmts will fail, in case we apply dump by enabling logging. + */ + if (!general_log_or_slow_log_tables(db, table)) + fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", + opt_quoted_table); + check_io(sql_file); + } + + field= mysql_fetch_field_direct(result, 0); + if (strcmp(field->name, "View") == 0) + { + char *scv_buff= NULL; + + verbose_msg("-- It's a view, create dummy view for view\n"); + + /* save "show create" statement for later */ + if ((row= mysql_fetch_row(result)) && (scv_buff=row[1])) + scv_buff= my_strdup(PSI_NOT_INSTRUMENTED, scv_buff, MYF(0)); + + mysql_free_result(result); + + /* + Create a view with the same name as the view and with columns of + the same name in order to satisfy views that depend on this view. + The view will be removed when the actual view is created. + + The properties of each column, are not preserved in this temporary + table, because they are not necessary. + + This will not be necessary once we can determine dependencies + between views and can simply dump them in the appropriate order. + */ + my_snprintf(query_buff, sizeof(query_buff), + "SHOW FIELDS FROM %s", result_table); + if (switch_character_set_results(mysql, "binary") || + mysql_query_with_error_report(mysql, &result, query_buff) || + switch_character_set_results(mysql, default_charset)) + { + /* + View references invalid or privileged table/col/fun (err 1356), + so we cannot create a stand-in table. Be defensive and dump + a comment with the view's 'show create' statement. (Bug #17371) + */ + + if (mysql_errno(mysql) == ER_VIEW_INVALID) + fprintf(sql_file, "\n-- failed on view %s: %s\n\n", result_table, scv_buff ? scv_buff : ""); + + my_free(scv_buff); + + if (path) + my_fclose(sql_file, MYF(MY_WME)); + DBUG_RETURN(0); + } + else + my_free(scv_buff); + + if (mysql_num_rows(result) != 0) + { + + if (opt_drop) + { + /* + We have already dropped any table of the same name above, so + here we just drop the view. + */ + + fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n", + opt_quoted_table); + check_io(sql_file); + } + + fprintf(sql_file, + "SET @saved_cs_client = @@character_set_client;\n" + "SET character_set_client = utf8;\n" + "/*!50001 CREATE VIEW %s AS SELECT\n", + result_table); + + /* + Get first row, following loop will prepend comma - keeps from + having to know if the row being printed is last to determine if + there should be a _trailing_ comma. + */ + + row= mysql_fetch_row(result); + + /* + The actual column value doesn't matter anyway, since the view will + be dropped at run time. + */ + fprintf(sql_file, " 1 AS %s", + quote_name(row[0], name_buff, 0)); + + while((row= mysql_fetch_row(result))) + { + /* col name, col type */ + fprintf(sql_file, ",\n 1 AS %s", + quote_name(row[0], name_buff, 0)); + } + + fprintf(sql_file, + " */;\n" + "SET character_set_client = @saved_cs_client;\n"); + + check_io(sql_file); + } + + mysql_free_result(result); + + if (path) + my_fclose(sql_file, MYF(MY_WME)); + + seen_views= 1; + DBUG_RETURN(0); + } + + row= mysql_fetch_row(result); + + is_log_table= general_log_or_slow_log_tables(db, table); + if (is_log_table) + row[1]+= 13; /* strlen("CREATE TABLE ")= 13 */ + create_table_str.str= row[1]; + if (opt_copy_s3_tables && (*ignore_flag & IGNORE_S3_TABLE) && + (s3_engine_ptr= strstr(row[1], s3_engine_token))) + { + init_dynamic_string_checked(&create_table_str, "", 1024, 1024); + dynstr_append_mem_checked(&create_table_str, row[1], + (uint)(s3_engine_ptr - row[1])); + dynstr_append_checked(&create_table_str, aria_engine_token); + dynstr_append_checked(&create_table_str, + s3_engine_ptr + sizeof(s3_engine_token) - 1); + } + if (opt_compatible_mode & 3) + { + fprintf(sql_file, + is_log_table ? "CREATE TABLE IF NOT EXISTS %s;\n" : "%s;\n", + create_table_str.str); + } + else + { + fprintf(sql_file, + "/*!40101 SET @saved_cs_client = @@character_set_client */;\n" + "/*!40101 SET character_set_client = utf8 */;\n" + "%s%s;\n" + "/*!40101 SET character_set_client = @saved_cs_client */;\n", + is_log_table ? "CREATE TABLE IF NOT EXISTS " : "", + create_table_str.str); + } + + check_io(sql_file); + if (create_table_str.str != row[1]) + dynstr_free(&create_table_str); + mysql_free_result(result); + } + my_snprintf(query_buff, sizeof(query_buff), + "select column_name, extra, generation_expression, data_type " + "from information_schema.columns where table_schema=database() " + "and table_name=%s order by ordinal_position", + quote_for_equal(table, temp_buff)); + if (mysql_query_with_error_report(mysql, &result, query_buff)) + { + if (path) + my_fclose(sql_file, MYF(MY_WME)); + DBUG_RETURN(0); + } + + while ((row= mysql_fetch_row(result))) + { + if (strstr(row[1],"INVISIBLE")) + complete_insert= 1; + if (vers_hidden && row[2] && strcmp(row[2], "ROW START") == 0) + { + vers_hidden= 0; + if (row[3] && strcmp(row[3], "bigint") == 0) + { + maybe_die(EX_ILLEGAL_TABLE, "Cannot use --dump-history for table %s with transaction-precise history", + result_table); + *versioned= 0; + } + } + if (init) + { + dynstr_append_checked(&select_field_names, ", "); + if (opt_header) + dynstr_append_checked(&select_field_names_for_header, ", "); + } + init=1; + dynstr_append_checked(&select_field_names, + quote_name(row[0], name_buff, 0)); + if (opt_header) + dynstr_append_checked(&select_field_names_for_header, + quote_for_equal(row[0], name_buff)); + } + + if (vers_hidden) + { + complete_insert= 1; + dynstr_append_checked(&select_field_names, ", row_start, row_end"); + } + + /* + If write_data is true, then we build up insert statements for + the table's data. Note: in subsequent lines of code, this test + will have to be performed each time we are appending to + insert_pat. + */ + if (write_data) + { + if (opt_replace_into) + dynstr_append_checked(&insert_pat, "REPLACE "); + else + dynstr_append_checked(&insert_pat, "INSERT "); + dynstr_append_checked(&insert_pat, insert_option); + dynstr_append_checked(&insert_pat, "INTO "); + dynstr_append_checked(&insert_pat, opt_quoted_table); + if (complete_insert) + { + dynstr_append_checked(&insert_pat, " ("); + } + else + { + if (extended_insert) + dynstr_append_checked(&insert_pat, " VALUES\n"); + else + dynstr_append_checked(&insert_pat, " VALUES ("); + } + } + + if (complete_insert) + dynstr_append_checked(&insert_pat, select_field_names.str); + num_fields= mysql_num_rows(result) + (vers_hidden ? 2 : 0); + mysql_free_result(result); + } + else + { + const char *show_fields_stmt= "SELECT `COLUMN_NAME` AS `Field`, " + "`COLUMN_TYPE` AS `Type`, " + "`IS_NULLABLE` AS `Null`, " + "`COLUMN_KEY` AS `Key`, " + "`COLUMN_DEFAULT` AS `Default`, " + "`EXTRA` AS `Extra`, " + "`COLUMN_COMMENT` AS `Comment` " + "FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE " + "TABLE_SCHEMA = %s AND TABLE_NAME = %s " + "ORDER BY ORDINAL_POSITION"; + + verbose_msg("%s: Warning: Can't set SQL_QUOTE_SHOW_CREATE option (%s)\n", + my_progname_short, mysql_error(mysql)); + + my_snprintf(query_buff, sizeof(query_buff), show_fields_stmt, + quote_for_equal(db, temp_buff), + quote_for_equal(table, temp_buff2)); + + if (mysql_query_with_error_report(mysql, &result, query_buff)) + DBUG_RETURN(0); + + /* Make an sql-file, if path was given iow. option -T was given */ + if (!opt_no_create_info) + { + if (path) + { + if (!(sql_file= open_sql_file_for_table(table, O_WRONLY))) + { + mysql_free_result(result); + DBUG_RETURN(0); + } + write_header(sql_file, db); + } + + print_comment(sql_file, 0, + "\n--\n-- Table structure for table %s\n--\n\n", + fix_for_comment(result_table)); + if (opt_drop) + fprintf(sql_file, "DROP TABLE IF EXISTS %s;\n", result_table); + if (!opt_xml) + fprintf(sql_file, "CREATE TABLE %s (\n", result_table); + else + print_xml_tag(sql_file, "\t", "\n", "table_structure", "name=", table, + NullS); + check_io(sql_file); + } + + if (write_data) + { + if (opt_replace_into) + dynstr_append_checked(&insert_pat, "REPLACE "); + else + dynstr_append_checked(&insert_pat, "INSERT "); + dynstr_append_checked(&insert_pat, insert_option); + dynstr_append_checked(&insert_pat, "INTO "); + dynstr_append_checked(&insert_pat, result_table); + if (complete_insert) + dynstr_append_checked(&insert_pat, " ("); + else + { + dynstr_append_checked(&insert_pat, " VALUES "); + if (!extended_insert) + dynstr_append_checked(&insert_pat, "("); + } + } + + while ((row= mysql_fetch_row(result))) + { + ulong *lengths= mysql_fetch_lengths(result); + if (init) + { + if (!opt_xml && !opt_no_create_info) + { + fputs(",\n",sql_file); + check_io(sql_file); + } + dynstr_append_checked(&select_field_names, ", "); + if (opt_header) + dynstr_append_checked(&select_field_names_for_header, ", "); + } + dynstr_append_checked(&select_field_names, + quote_name(row[SHOW_FIELDNAME], name_buff, 0)); + if (opt_header) + dynstr_append_checked(&select_field_names_for_header, + quote_for_equal(row[SHOW_FIELDNAME], name_buff)); + init=1; + if (!opt_no_create_info) + { + if (opt_xml) + { + print_xml_row(sql_file, "field", result, &row, NullS); + continue; + } + + if (opt_keywords) + fprintf(sql_file, " %s.%s %s", result_table, + quote_name(row[SHOW_FIELDNAME],name_buff, 0), row[SHOW_TYPE]); + else + fprintf(sql_file, " %s %s", + quote_name(row[SHOW_FIELDNAME], name_buff, 0), row[SHOW_TYPE]); + if (row[SHOW_DEFAULT]) + { + fputs(" DEFAULT ", sql_file); + unescape(sql_file, row[SHOW_DEFAULT], lengths[SHOW_DEFAULT]); + } + if (!row[SHOW_NULL][0]) + fputs(" NOT NULL", sql_file); + if (row[SHOW_EXTRA][0]) + fprintf(sql_file, " %s",row[SHOW_EXTRA]); + check_io(sql_file); + } + } + if (complete_insert) + dynstr_append_checked(&insert_pat, select_field_names.str); + num_fields= mysql_num_rows(result); + mysql_free_result(result); + if (!opt_no_create_info) + { + /* Make an sql-file, if path was given iow. option -T was given */ + char buff[20+FN_REFLEN]; + uint keynr,primary_key; + my_snprintf(buff, sizeof(buff), "show keys from %s", result_table); + if (mysql_query_with_error_report(mysql, &result, buff)) + { + if (mysql_errno(mysql) == ER_WRONG_OBJECT) + { + /* it is VIEW */ + fputs("\t\t\n", sql_file); + goto continue_xml; + } + fprintf(stderr, "%s: Can't get keys for table %s (%s)\n", + my_progname_short, result_table, mysql_error(mysql)); + if (path) + my_fclose(sql_file, MYF(MY_WME)); + DBUG_RETURN(0); + } + + /* Find first which key is primary key */ + keynr=0; + primary_key=INT_MAX; + while ((row= mysql_fetch_row(result))) + { + if (atoi(row[3]) == 1) + { + keynr++; +#ifdef FORCE_PRIMARY_KEY + if (atoi(row[1]) == 0 && primary_key == INT_MAX) + primary_key=keynr; +#endif + if (!strcmp(row[2],"PRIMARY")) + { + primary_key=keynr; + break; + } + } + } + mysql_data_seek(result,0); + keynr=0; + while ((row= mysql_fetch_row(result))) + { + if (opt_xml) + { + print_xml_row(sql_file, "key", result, &row, NullS); + continue; + } + + if (atoi(row[3]) == 1) + { + if (keynr++) + putc(')', sql_file); + if (atoi(row[1])) /* Test if duplicate key */ + /* Duplicate allowed */ + fprintf(sql_file, ",\n KEY %s (",quote_name(row[2],name_buff,0)); + else if (keynr == primary_key) + fputs(",\n PRIMARY KEY (",sql_file); /* First UNIQUE is primary */ + else + fprintf(sql_file, ",\n UNIQUE %s (",quote_name(row[2],name_buff, + 0)); + } + else + putc(',', sql_file); + fputs(quote_name(row[4], name_buff, 0), sql_file); + if (row[7]) + fprintf(sql_file, " (%s)",row[7]); /* Sub key */ + check_io(sql_file); + } + mysql_free_result(result); + if (!opt_xml) + { + if (keynr) + putc(')', sql_file); + fputs("\n)",sql_file); + check_io(sql_file); + } + + /* Get MySQL specific create options */ + if (create_options) + { + char show_name_buff[NAME_LEN*2+2+24]; + + /* Check memory for quote_for_like() */ + my_snprintf(buff, sizeof(buff), "show table status like %s", + quote_for_like(table, show_name_buff)); + + if (mysql_query_with_error_report(mysql, &result, buff)) + { + if (mysql_errno(mysql) != ER_PARSE_ERROR) + { /* If old MySQL version */ + verbose_msg("-- Warning: Couldn't get status information for " \ + "table %s (%s)\n", result_table,mysql_error(mysql)); + } + } + else if (!(row= mysql_fetch_row(result))) + { + fprintf(stderr, + "Error: Couldn't read status information for table %s (%s)\n", + result_table,mysql_error(mysql)); + } + else + { + if (opt_xml) + print_xml_row(sql_file, "options", result, &row, NullS); + else + { + fputs("/*!",sql_file); + print_value(sql_file,result,row,"engine=","Engine",0); + print_value(sql_file,result,row,"","Create_options",0); + print_value(sql_file,result,row,"comment=","Comment",1); + fputs(" */",sql_file); + check_io(sql_file); + } + } + mysql_free_result(result); /* Is always safe to free */ + } +continue_xml: + if (!opt_xml) + fputs(";\n", sql_file); + else + fputs("\t\n", sql_file); + check_io(sql_file); + } + } + if (complete_insert) + { + dynstr_append_checked(&insert_pat, ") VALUES "); + if (!extended_insert) + dynstr_append_checked(&insert_pat, "("); + } + if (sql_file != md_result_file) + { + fputs("\n", sql_file); + write_footer(sql_file); + my_fclose(sql_file, MYF(MY_WME)); + } + DBUG_RETURN((uint) num_fields); +} /* get_table_structure */ + +static void dump_trigger_old(FILE *sql_file, MYSQL_RES *show_triggers_rs, + MYSQL_ROW *show_trigger_row, + const char *table_name) +{ + char quoted_table_name_buf[NAME_LEN * 2 + 3]; + char *quoted_table_name= quote_name(table_name, quoted_table_name_buf, 1); + + char name_buff[NAME_LEN * 4 + 3]; + const char *xml_msg= "\nWarning! mysqldump being run against old server " + "that does not\nsupport 'SHOW CREATE TRIGGER' " + "statement. Skipping..\n"; + + DBUG_ENTER("dump_trigger_old"); + + if (opt_xml) + { + print_xml_comment(sql_file, strlen(xml_msg), xml_msg); + check_io(sql_file); + DBUG_VOID_RETURN; + } + + fprintf(sql_file, + "--\n" + "-- WARNING: old server version. " + "The following dump may be incomplete.\n" + "--\n"); + + if (opt_compact) + fprintf(sql_file, "/*!50003 SET @OLD_SQL_MODE=@@SQL_MODE*/;\n"); + + if (opt_drop_trigger) + fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", + (*show_trigger_row)[0]); + + fprintf(sql_file, + "DELIMITER ;;\n" + "/*!50003 SET SESSION SQL_MODE=\"%s\" */;;\n" + "/*!50003 CREATE */ ", + (*show_trigger_row)[6]); + + if (mysql_num_fields(show_triggers_rs) > 7) + { + /* + mysqldump can be run against the server, that does not support + definer in triggers (there is no DEFINER column in SHOW TRIGGERS + output). So, we should check if we have this column before + accessing it. + */ + + size_t user_name_len; + char user_name_str[USERNAME_LENGTH + 1]; + char quoted_user_name_str[USERNAME_LENGTH * 2 + 3]; + size_t host_name_len; + char host_name_str[HOSTNAME_LENGTH + 1]; + char quoted_host_name_str[HOSTNAME_LENGTH * 2 + 3]; + + parse_user((*show_trigger_row)[7], + strlen((*show_trigger_row)[7]), + user_name_str, &user_name_len, + host_name_str, &host_name_len); + + fprintf(sql_file, + "/*!50017 DEFINER=%s@%s */ ", + quote_name(user_name_str, quoted_user_name_str, FALSE), + quote_name(host_name_str, quoted_host_name_str, FALSE)); + } + + fprintf(sql_file, + "/*!50003 TRIGGER %s %s %s ON %s FOR EACH ROW%s%s */;;\n" + "DELIMITER ;\n", + quote_name((*show_trigger_row)[0], name_buff, 0), /* Trigger */ + (*show_trigger_row)[4], /* Timing */ + (*show_trigger_row)[1], /* Event */ + quoted_table_name, + (strchr(" \t\n\r", *((*show_trigger_row)[3]))) ? "" : " ", + (*show_trigger_row)[3] /* Statement */); + + if (opt_compact) + fprintf(sql_file, "/*!50003 SET SESSION SQL_MODE=@OLD_SQL_MODE */;\n"); + + DBUG_VOID_RETURN; +} + +static int dump_trigger(FILE *sql_file, MYSQL_RES *show_create_trigger_rs, + const char *db_name, + const char *db_cl_name) +{ + MYSQL_ROW row; + char *query_str; + int db_cl_altered= FALSE; + + DBUG_ENTER("dump_trigger"); + + while ((row= mysql_fetch_row(show_create_trigger_rs))) + { + if (opt_xml) + { + print_xml_row(sql_file, "trigger", show_create_trigger_rs, &row, + "SQL Original Statement"); + check_io(sql_file); + continue; + } + + if (switch_db_collation(sql_file, db_name, ";", + db_cl_name, row[5], &db_cl_altered)) + DBUG_RETURN(TRUE); + + switch_cs_variables(sql_file, ";", + row[3], /* character_set_client */ + row[3], /* character_set_results */ + row[4]); /* collation_connection */ + + switch_sql_mode(sql_file, ";", row[1]); + + if (opt_drop_trigger) + fprintf(sql_file, "/*!50032 DROP TRIGGER IF EXISTS %s */;\n", + row[0]); + + query_str= cover_definer_clause(row[2], strlen(row[2]), + C_STRING_WITH_LEN("50017"), + C_STRING_WITH_LEN("50003"), + C_STRING_WITH_LEN(" TRIGGER")); + fprintf(sql_file, + "DELIMITER ;;\n" + "/*!50003 %s */;;\n" + "DELIMITER ;\n", + (const char *) (query_str != NULL ? query_str : row[2])); + + my_free(query_str); + + restore_sql_mode(sql_file, ";"); + restore_cs_variables(sql_file, ";"); + + if (db_cl_altered) + { + if (restore_db_collation(sql_file, db_name, ";", db_cl_name)) + DBUG_RETURN(TRUE); + } + } + + DBUG_RETURN(FALSE); +} + +/** + Dump the triggers for a given table. + + This should be called after the tables have been dumped in case a trigger + depends on the existence of a table. + + @param[in] table_name + @param[in] db_name + + @return Error status. + @retval TRUE error has occurred. + @retval FALSE operation succeed. +*/ + +static int dump_triggers_for_table(char *table_name, char *db_name) +{ + char name_buff[NAME_LEN*4+3]; + char query_buff[QUERY_LENGTH]; + uint old_opt_compatible_mode= opt_compatible_mode; + MYSQL_RES *show_triggers_rs= NULL; + MYSQL_ROW row; + FILE *sql_file= md_result_file; + + char db_cl_name[MY_CS_COLLATION_NAME_SIZE]; + int ret= TRUE; + /* Servers below 5.1.21 do not support SHOW CREATE TRIGGER */ + const int use_show_create_trigger= mysql_get_server_version(mysql) >= 50121; + + DBUG_ENTER("dump_triggers_for_table"); + DBUG_PRINT("enter", ("db: %s, table_name: %s", db_name, table_name)); + + if (path && + !(sql_file= open_sql_file_for_table(table_name, O_WRONLY | O_APPEND))) + DBUG_RETURN(1); + + /* Do not use ANSI_QUOTES on triggers in dump */ + opt_compatible_mode&= ~MASK_ANSI_QUOTES; + + /* Get database collation. */ + + if (switch_character_set_results(mysql, "binary")) + goto done; + + if (fetch_db_collation(db_name, db_cl_name, sizeof (db_cl_name))) + goto done; + + /* Get list of triggers. */ + + if (use_show_create_trigger) + my_snprintf(query_buff, sizeof(query_buff), + "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS " + "WHERE EVENT_OBJECT_SCHEMA = DATABASE() AND " + "EVENT_OBJECT_TABLE = %s", + quote_for_equal(table_name, name_buff)); + else + my_snprintf(query_buff, sizeof(query_buff), "SHOW TRIGGERS LIKE %s", + quote_for_like(table_name, name_buff)); + + if (mysql_query_with_error_report(mysql, &show_triggers_rs, query_buff)) + goto done; + + /* Dump triggers. */ + + if (! mysql_num_rows(show_triggers_rs)) + goto skip; + + if (opt_xml) + print_xml_tag(sql_file, "\t", "\n", "triggers", "name=", + table_name, NullS); + + while ((row= mysql_fetch_row(show_triggers_rs))) + { + if (use_show_create_trigger) + { + MYSQL_RES *show_create_trigger_rs; + + my_snprintf(query_buff, sizeof (query_buff), "SHOW CREATE TRIGGER %s", + quote_name(row[0], name_buff, TRUE)); + + if (mysql_query_with_error_report(mysql, &show_create_trigger_rs, + query_buff)) + goto done; + else + { + int error= (!show_create_trigger_rs || + dump_trigger(sql_file, show_create_trigger_rs, db_name, + db_cl_name)); + mysql_free_result(show_create_trigger_rs); + if (error) + goto done; + } + } + else + dump_trigger_old(sql_file, show_triggers_rs, &row, table_name); + } + + if (opt_xml) + { + fputs("\t\n", sql_file); + check_io(sql_file); + } + +skip: + if (switch_character_set_results(mysql, default_charset)) + goto done; + + /* + make sure to set back opt_compatible mode to + original value + */ + opt_compatible_mode=old_opt_compatible_mode; + + ret= FALSE; + +done: + if (path) + my_fclose(sql_file, MYF(0)); + mysql_free_result(show_triggers_rs); + DBUG_RETURN(ret); +} + +static void add_load_option(DYNAMIC_STRING *str, const char *option, + const char *option_value) +{ + if (!option_value) + { + /* Null value means we don't add this option. */ + return; + } + + dynstr_append_checked(str, option); + + if (strncmp(option_value, "0x", sizeof("0x")-1) == 0) + { + /* It's a hex constant, don't escape */ + dynstr_append_checked(str, option_value); + } + else + { + /* char constant; escape */ + field_escape(str, option_value); + } +} + + +/* + Allow the user to specify field terminator strings like: + "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline) + This is done by doubling ' and add a end -\ if needed to avoid + syntax errors from the SQL parser. +*/ + +static void field_escape(DYNAMIC_STRING* in, const char *from) +{ + uint end_backslashes= 0; + + dynstr_append_checked(in, "'"); + + while (*from) + { + dynstr_append_mem_checked(in, from, 1); + + if (*from == '\\') + end_backslashes^=1; /* find odd number of backslashes */ + else + { + if (*from == '\'' && !end_backslashes) + { + /* We want a duplicate of "'" for MySQL */ + dynstr_append_checked(in, "\'"); + } + end_backslashes=0; + } + from++; + } + /* Add missing backslashes if user has specified odd number of backs.*/ + if (end_backslashes) + dynstr_append_checked(in, "\\"); + + dynstr_append_checked(in, "'"); +} + + + +static char *alloc_query_str(size_t size) +{ + char *query; + + if (!(query= (char*) my_malloc(PSI_NOT_INSTRUMENTED, size, MYF(MY_WME)))) + die(EX_MYSQLERR, "Couldn't allocate a query string."); + + return query; +} + + +static void vers_append_system_time(DYNAMIC_STRING* query_string) +{ + if (opt_dump_history) + dynstr_append_checked(query_string, " FOR SYSTEM_TIME ALL"); + else + { + DBUG_ASSERT(opt_asof_timestamp); + dynstr_append_checked(query_string, " FOR SYSTEM_TIME AS OF TIMESTAMP '"); + dynstr_append_checked(query_string, opt_asof_timestamp); + dynstr_append_checked(query_string, "'"); + } +} + + +/* + + SYNOPSIS + dump_table() + + dump_table saves database contents as a series of INSERT statements. + + ARGS + table - table name + db - db name + + RETURNS + void +*/ + + +static void dump_table(const char *table, const char *db, const uchar *hash_key, size_t len) +{ + char ignore_flag; + char buf[200], table_buff[NAME_LEN+3]; + DYNAMIC_STRING query_string; + char table_type[NAME_LEN]; + char *result_table, table_buff2[NAME_LEN*2+3], *opt_quoted_table; + int error= 0; + ulong rownr, row_break; + uint num_fields; + size_t total_length, init_length; + my_bool versioned= 0; + + MYSQL_RES *res= NULL; + MYSQL_FIELD *field; + MYSQL_ROW row; + DBUG_ENTER("dump_table"); + + /* + Make sure you get the create table info before the following check for + --no-data flag below. Otherwise, the create table info won't be printed. + */ + num_fields= get_table_structure(table, db, table_type, &ignore_flag, &versioned); + + /* + The "table" could be a view. If so, we don't do anything here. + */ + if (strcmp(table_type, "VIEW") == 0) + DBUG_VOID_RETURN; + + if (!opt_copy_s3_tables && (ignore_flag & IGNORE_S3_TABLE)) + { + verbose_msg("-- Skipping dump data for table '%s', " + " this is S3 table and --copy-s3-tables=0\n", + table); + DBUG_VOID_RETURN; + } + + /* Check --no-data flag */ + if (opt_no_data || (hash_key && ignore_table_data(hash_key, len))) + { + verbose_msg("-- Skipping dump data for table '%s', --no-data was used\n", + table); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", + ("ignore_flag: %x num_fields: %d", (int) ignore_flag, + num_fields)); + /* + If the table type is a merge table or any type that has to be + _completely_ ignored and no data dumped + */ + if (ignore_flag & IGNORE_DATA) + { + verbose_msg("-- Warning: Skipping data for table '%s' because " \ + "it's of type %s\n", table, table_type); + DBUG_VOID_RETURN; + } + /* Check that there are any fields in the table */ + if (num_fields == 0) + { + verbose_msg("-- Skipping dump data for table '%s', it has no fields\n", + table); + DBUG_VOID_RETURN; + } + + /* + Check --skip-events flag: it is not enough to skip creation of events + discarding SHOW CREATE EVENT statements generation. The myslq.event + table data should be skipped too. + */ + if (!opt_events && !my_strcasecmp(&my_charset_latin1, db, "mysql") && + !my_strcasecmp(&my_charset_latin1, table, "event")) + { + verbose_msg("-- Skipping data table mysql.event, --skip-events was used\n"); + DBUG_VOID_RETURN; + } + + result_table= quote_name(table,table_buff, 1); + opt_quoted_table= quote_name(table, table_buff2, 0); + + verbose_msg("-- Sending SELECT query...\n"); + + init_dynamic_string_checked(&query_string, "", 1024, 1024); + + if (path) + { + char filename[FN_REFLEN], tmp_path[FN_REFLEN]; + /* + Convert the path to native os format + and resolve to the full filepath. + */ + convert_dirname(tmp_path,path,NullS); + my_load_path(tmp_path, tmp_path, NULL); + fn_format(filename, table, tmp_path, ".txt", MYF(MY_UNPACK_FILENAME)); + + /* Must delete the file that 'INTO OUTFILE' will write to */ + my_delete(filename, MYF(0)); + + /* convert to a unix path name to stick into the query */ + to_unix_path(filename); + + /* now build the query string */ + + dynstr_append_checked(&query_string, "SELECT /*!40001 SQL_NO_CACHE */ "); + dynstr_append_checked(&query_string, select_field_names.str); + dynstr_append_checked(&query_string, " INTO OUTFILE '"); + dynstr_append_checked(&query_string, filename); + dynstr_append_checked(&query_string, "'"); + + dynstr_append_checked(&query_string, " /*!50138 CHARACTER SET "); + dynstr_append_checked(&query_string, default_charset == mysql_universal_client_charset ? + my_charset_bin.coll_name.str : /* backward compatibility */ + default_charset); + dynstr_append_checked(&query_string, " */"); + + if (fields_terminated || enclosed || opt_enclosed || escaped) + dynstr_append_checked(&query_string, " FIELDS"); + + add_load_option(&query_string, " TERMINATED BY ", fields_terminated); + add_load_option(&query_string, " ENCLOSED BY ", enclosed); + add_load_option(&query_string, " OPTIONALLY ENCLOSED BY ", opt_enclosed); + add_load_option(&query_string, " ESCAPED BY ", escaped); + add_load_option(&query_string, " LINES TERMINATED BY ", lines_terminated); + + if (opt_header) + { + dynstr_append_checked(&query_string, " FROM ( SELECT "); + if (order_by) + dynstr_append_checked(&query_string, " 0 AS `_$is_data_row$_`,"); + dynstr_append_checked(&query_string, select_field_names_for_header.str); + dynstr_append_checked(&query_string, " UNION ALL SELECT "); + if (order_by) + dynstr_append_checked(&query_string, "1 AS `_$is_data_row$_`,"); + dynstr_append_checked(&query_string, select_field_names.str); + } + dynstr_append_checked(&query_string, " FROM "); + dynstr_append_checked(&query_string, result_table); + + if (versioned) + vers_append_system_time(&query_string); + + if (where) + { + dynstr_append_checked(&query_string, " WHERE "); + dynstr_append_checked(&query_string, where); + } + if (opt_header) + dynstr_append_checked(&query_string, ") s"); + + if (order_by) + { + if (opt_header) + dynstr_append_checked(&query_string, " ORDER BY `_$is_data_row$_`,"); + else + dynstr_append_checked(&query_string, " ORDER BY "); + dynstr_append_checked(&query_string, order_by); + my_free(order_by); + order_by= 0; + } + + if (mysql_real_query(mysql, query_string.str, (ulong)query_string.length)) + { + dynstr_free(&query_string); + DB_error(mysql, "when executing 'SELECT INTO OUTFILE'"); + DBUG_VOID_RETURN; + } + } + else + { + print_comment(md_result_file, 0, + "\n--\n-- Dumping data for table %s\n--\n", + fix_for_comment(result_table)); + + dynstr_append_checked(&query_string, "SELECT /*!40001 SQL_NO_CACHE */ "); + dynstr_append_checked(&query_string, select_field_names.str); + dynstr_append_checked(&query_string, " FROM "); + dynstr_append_checked(&query_string, result_table); + if (versioned) + vers_append_system_time(&query_string); + + if (where) + { + print_comment(md_result_file, 0, "-- WHERE: %s\n", fix_for_comment(where)); + + dynstr_append_checked(&query_string, " WHERE "); + dynstr_append_checked(&query_string, where); + } + if (order_by) + { + print_comment(md_result_file, 0, "-- ORDER BY: %s\n", fix_for_comment(order_by)); + + dynstr_append_checked(&query_string, " ORDER BY "); + dynstr_append_checked(&query_string, order_by); + my_free(order_by); + order_by= 0; + } + + if (!opt_xml && !opt_compact) + { + fputs("\n", md_result_file); + check_io(md_result_file); + } + if (mysql_query_with_error_report(mysql, 0, query_string.str)) + { + dynstr_free(&query_string); + DB_error(mysql, "when retrieving data from server"); + goto err; + } + if (quick) + res=mysql_use_result(mysql); + else + res=mysql_store_result(mysql); + if (!res) + { + dynstr_free(&query_string); + DB_error(mysql, "when retrieving data from server"); + goto err; + } + + verbose_msg("-- Retrieving rows...\n"); + if (mysql_num_fields(res) != num_fields) + { + fprintf(stderr,"%s: Error in field count for table: %s ! Aborting.\n", + my_progname_short, result_table); + error= EX_CONSCHECK; + if (!quick) + mysql_free_result(res); + goto err; + } + + if (versioned && !opt_xml && opt_dump_history) + { + fprintf(md_result_file,"/*!101100 SET @old_system_versioning_insert_history=@@session.system_versioning_insert_history, @@session.system_versioning_insert_history=1 */;\n"); + check_io(md_result_file); + } + if (opt_lock) + { + fprintf(md_result_file,"LOCK TABLES %s WRITE;\n", opt_quoted_table); + check_io(md_result_file); + } + /* Moved disable keys to after lock per bug 15977 */ + if (opt_disable_keys) + { + fprintf(md_result_file, "/*!40000 ALTER TABLE %s DISABLE KEYS */;\n", + opt_quoted_table); + check_io(md_result_file); + } + + total_length= opt_net_buffer_length; /* Force row break */ + row_break=0; + rownr=0; + init_length=(uint) insert_pat.length+4; + if (opt_xml) + print_xml_tag(md_result_file, "\t", "\n", "table_data", "name=", table, + NullS); + if (opt_autocommit) + { + fprintf(md_result_file, "set autocommit=0;\n"); + check_io(md_result_file); + } + + while ((row= mysql_fetch_row(res))) + { + uint i; + ulong *lengths= mysql_fetch_lengths(res); + rownr++; + if (!extended_insert && !opt_xml) + { + fputs(insert_pat.str,md_result_file); + check_io(md_result_file); + } + mysql_field_seek(res,0); + + if (opt_xml) + { + fputs("\t\n", md_result_file); + check_io(md_result_file); + } + + for (i= 0; i < mysql_num_fields(res); i++) + { + int is_blob; + ulong length= lengths[i]; + + if (!(field= mysql_fetch_field(res))) + die(EX_CONSCHECK, + "Not enough fields from table %s! Aborting.\n", + result_table); + + /* + 63 is my_charset_bin. If charsetnr is not 63, + we have not a BLOB but a TEXT column. + we'll dump in hex only BLOB columns. + */ + is_blob= (opt_hex_blob && field->charsetnr == 63 && + (field->type == MYSQL_TYPE_BIT || + field->type == MYSQL_TYPE_STRING || + field->type == MYSQL_TYPE_VAR_STRING || + field->type == MYSQL_TYPE_VARCHAR || + 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_GEOMETRY)) ? 1 : 0; + if (extended_insert && !opt_xml) + { + if (i == 0) + dynstr_set_checked(&extended_row,"("); + else + dynstr_append_checked(&extended_row,","); + + if (row[i]) + { + if (length) + { + if (!(field->flags & NUM_FLAG)) + { + /* + "length * 2 + 2" is OK for both HEX and non-HEX modes: + - In HEX mode we need exactly 2 bytes per character + plus 2 bytes for '0x' prefix. + - In non-HEX mode we need up to 2 bytes per character, + plus 2 bytes for leading and trailing '\'' characters. + Also we need to reserve 1 byte for terminating '\0'. + */ + dynstr_realloc_checked(&extended_row,length * 2 + 2 + 1); + if (opt_hex_blob && is_blob) + { + dynstr_append_checked(&extended_row, "0x"); + extended_row.length+= mysql_hex_string(extended_row.str + + extended_row.length, + row[i], length); + DBUG_ASSERT(extended_row.length+1 <= extended_row.max_length); + /* mysql_hex_string() already terminated string by '\0' */ + DBUG_ASSERT(extended_row.str[extended_row.length] == '\0'); + } + else + { + dynstr_append_checked(&extended_row,"'"); + extended_row.length += + mysql_real_escape_string(&mysql_connection, + &extended_row.str[extended_row.length], + row[i],length); + extended_row.str[extended_row.length]='\0'; + dynstr_append_checked(&extended_row,"'"); + } + } + else + { + /* change any strings ("inf", "-inf", "nan") into NULL */ + char *ptr= row[i]; + if (my_isalpha(charset_info, *ptr) || (*ptr == '-' && + my_isalpha(charset_info, ptr[1]))) + dynstr_append_checked(&extended_row, "NULL"); + else + { + if (field->type == MYSQL_TYPE_DECIMAL) + { + /* add " signs around */ + dynstr_append_checked(&extended_row, "'"); + dynstr_append_checked(&extended_row, ptr); + dynstr_append_checked(&extended_row, "'"); + } + else + dynstr_append_checked(&extended_row, ptr); + } + } + } + else + dynstr_append_checked(&extended_row,"''"); + } + else + dynstr_append_checked(&extended_row,"NULL"); + } + else + { + if (i && !opt_xml) + { + fputc(',', md_result_file); + check_io(md_result_file); + } + if (row[i]) + { + if (!(field->flags & NUM_FLAG)) + { + if (opt_xml) + { + if (opt_hex_blob && is_blob && length) + { + /* Define xsi:type="xs:hexBinary" for hex encoded data */ + print_xml_tag(md_result_file, "\t\t", "", "field", "name=", + field->name, "xsi:type=", "xs:hexBinary", NullS); + print_blob_as_hex(md_result_file, row[i], length); + } + else + { + print_xml_tag(md_result_file, "\t\t", "", "field", "name=", + field->name, NullS); + print_quoted_xml(md_result_file, row[i], length, 0); + } + fputs("\n", md_result_file); + } + else if (opt_hex_blob && is_blob && length) + { + fputs("0x", md_result_file); + print_blob_as_hex(md_result_file, row[i], length); + } + else + unescape(md_result_file, row[i], length); + } + else + { + /* change any strings ("inf", "-inf", "nan") into NULL */ + char *ptr= row[i]; + if (opt_xml) + { + print_xml_tag(md_result_file, "\t\t", "", "field", "name=", + field->name, NullS); + fputs(!my_isalpha(charset_info, *ptr) ? ptr: "NULL", + md_result_file); + fputs("\n", md_result_file); + } + else if (my_isalpha(charset_info, *ptr) || + (*ptr == '-' && my_isalpha(charset_info, ptr[1]))) + fputs("NULL", md_result_file); + else if (field->type == MYSQL_TYPE_DECIMAL) + { + /* add " signs around */ + fputc('\'', md_result_file); + fputs(ptr, md_result_file); + fputc('\'', md_result_file); + } + else + fputs(ptr, md_result_file); + } + } + else + { + /* The field value is NULL */ + if (!opt_xml) + fputs("NULL", md_result_file); + else + print_xml_null_tag(md_result_file, "\t\t", "field name=", + field->name, "\n"); + } + check_io(md_result_file); + } + } + + if (opt_xml) + { + fputs("\t\n", md_result_file); + check_io(md_result_file); + } + + if (extended_insert) + { + size_t row_length; + dynstr_append_checked(&extended_row,")"); + row_length= 2 + extended_row.length; + if (total_length + row_length < opt_net_buffer_length) + { + total_length+= row_length; + fputs(",\n",md_result_file); /* Always row break */ + fputs(extended_row.str,md_result_file); + } + else + { + if (row_break) + fputs(";\n", md_result_file); + row_break=1; /* This is first row */ + + fputs(insert_pat.str,md_result_file); + fputs(extended_row.str,md_result_file); + total_length= row_length+init_length; + } + check_io(md_result_file); + } + else if (!opt_xml) + { + fputs(");\n", md_result_file); + check_io(md_result_file); + } + } + + /* XML - close table tag and suppress regular output */ + if (opt_xml) + fputs("\t\n", md_result_file); + else if (extended_insert && row_break) + fputs(";\n", md_result_file); /* If not empty table */ + if (!opt_xml && opt_copy_s3_tables && (ignore_flag & IGNORE_S3_TABLE)) + { + DYNAMIC_STRING alter_string; + init_dynamic_string_checked(&alter_string, "ALTER TABLE ", 1024, 1024); + dynstr_append_checked(&alter_string, opt_quoted_table); + dynstr_append_checked(&alter_string, " ENGINE=S3;\n"); + fputs(alter_string.str, md_result_file); + dynstr_free(&alter_string); + } + fflush(md_result_file); + check_io(md_result_file); + if (mysql_errno(mysql)) + { + my_snprintf(buf, sizeof(buf), + "%s: Error %d: %s when dumping table %s at row: %ld\n", + my_progname_short, + mysql_errno(mysql), + mysql_error(mysql), + result_table, + rownr); + fputs(buf,stderr); + error= EX_CONSCHECK; + goto err; + } + + /* Moved enable keys to before unlock per bug 15977 */ + if (opt_disable_keys) + { + fprintf(md_result_file,"/*!40000 ALTER TABLE %s ENABLE KEYS */;\n", + opt_quoted_table); + check_io(md_result_file); + } + if (opt_lock) + { + fputs("UNLOCK TABLES;\n", md_result_file); + check_io(md_result_file); + } + if (opt_autocommit) + { + fprintf(md_result_file, "commit;\n"); + check_io(md_result_file); + } + if (versioned && !opt_xml && opt_dump_history) + { + fprintf(md_result_file,"/*!101100 SET system_versioning_insert_history=@old_system_versioning_insert_history */;\n"); + check_io(md_result_file); + } + mysql_free_result(res); + } + dynstr_free(&query_string); + DBUG_VOID_RETURN; + +err: + dynstr_free(&query_string); + maybe_exit(error); + mysql_free_result(res); + DBUG_VOID_RETURN; +} /* dump_table */ + + +static char *getTableName(int reset, int want_sequences) +{ + MYSQL_ROW row; + const char *query; + + if (!get_table_name_result) + { + if (opt_order_by_size || mysql_get_server_version(mysql) >= FIRST_SEQUENCE_VERSION) + { + if (opt_order_by_size) { + query= "SELECT table_name, table_type FROM INFORMATION_SCHEMA.TABLES " + "WHERE table_schema = DATABASE() ORDER BY data_length, table_name"; + } else { + query = "SHOW FULL TABLES"; + } + if (mysql_query_with_error_report(mysql, 0, query)) + return (NULL); + + if (!(get_table_name_result= mysql_store_result(mysql))) + return (NULL); + } + else + { + if (!(get_table_name_result= mysql_list_tables(mysql,NullS))) + return(NULL); + } + } + if ((row= mysql_fetch_row(get_table_name_result))) + { + if (want_sequences != DUMP_TABLE_ALL) + while (row && MY_TEST(strcmp(row[1], "SEQUENCE")) == want_sequences) + row= mysql_fetch_row(get_table_name_result); + + if (row) + return((char*) row[0]); + } + if (reset) + mysql_data_seek(get_table_name_result,0); /* We want to read again */ + else + { + mysql_free_result(get_table_name_result); + get_table_name_result= NULL; + } + return(NULL); +} /* getTableName */ + + +/* + dump user/role grants + ARGS + user_role: is either a user, or a role +*/ + +static int dump_grants(const char *user_role) +{ + DYNAMIC_STRING sqlbuf; + MYSQL_ROW row; + MYSQL_RES *tableres; + + init_dynamic_string_checked(&sqlbuf, "SHOW GRANTS FOR ", 256, 1024); + dynstr_append_checked(&sqlbuf, user_role); + + if (mysql_query_with_error_report(mysql, &tableres, sqlbuf.str)) + { + dynstr_free(&sqlbuf); + return 1; + } + while ((row= mysql_fetch_row(tableres))) + { + if (strncmp(row[0], "SET DEFAULT ROLE", sizeof("SET DEFAULT ROLE") - 1) == 0) + continue; + fprintf(md_result_file, "%s;\n", row[0]); + } + mysql_free_result(tableres); + dynstr_free(&sqlbuf); + return 0; +} + + +/* + dump create user +*/ + +static int dump_create_user(const char *user) +{ + DYNAMIC_STRING sqlbuf; + MYSQL_ROW row; + MYSQL_RES *tableres; + + init_dynamic_string_checked(&sqlbuf, "SHOW CREATE USER ", 256, 1024); + dynstr_append_checked(&sqlbuf, user); + + if (mysql_query_with_error_report(mysql, &tableres, sqlbuf.str)) + { + dynstr_free(&sqlbuf); + return 1; + } + while ((row= mysql_fetch_row(tableres))) + { + fprintf(md_result_file, "CREATE %sUSER %s%s;\n", opt_replace_into ? "/*M!100103 OR REPLACE */ ": "", + opt_ignore ? "IF NOT EXISTS " : "", + row[0] + sizeof("CREATE USER")); + } + mysql_free_result(tableres); + dynstr_free(&sqlbuf); + return 0; +} + + +/* + dump all users, roles and their grants +*/ + +static int dump_all_users_roles_and_grants() +{ + MYSQL_ROW row; + MYSQL_RES *tableres; + int result= 0; + /* Roles added in MariaDB-10.0.5 or MySQL-8.0 */ + my_bool maria_roles_exist= (mysql_get_server_version(mysql) >= 100005); + my_bool mysql_roles_exist= (mysql_get_server_version(mysql) >= 80001) && !maria_roles_exist; + + if (mysql_query_with_error_report(mysql, &tableres, + "SELECT CONCAT(QUOTE(u.user), '@', QUOTE(u.Host)) AS u " + "FROM mysql.user u " + " /*!80001 LEFT JOIN mysql.role_edges e " + " ON u.user=e.from_user " + " AND u.host=e.from_host " + " WHERE e.from_user IS NULL */" + " /*M!100005 WHERE is_role='N' */")) + return 1; + while ((row= mysql_fetch_row(tableres))) + { + if (opt_replace_into) + /* Protection against removing the current import user */ + /* MySQL-8.0 export capability */ + fprintf(md_result_file, + "DELIMITER |\n" + "/*M!100101 IF current_user()=\"%s\" THEN\n" + " SIGNAL SQLSTATE '45000' SET MYSQL_ERRNO=30001," + " MESSAGE_TEXT=\"Don't remove current user %s'\";\n" + "END IF */|\n" + "DELIMITER ;\n" + "/*!50701 DROP USER IF EXISTS %s */;\n", row[0], row[0], row[0]); + if (dump_create_user(row[0])) + result= 1; + /* if roles exist, defer dumping grants until after roles created */ + if (maria_roles_exist || mysql_roles_exist) + continue; + if (dump_grants(row[0])) + result= 1; + } + mysql_free_result(tableres); + + if (!(maria_roles_exist || mysql_roles_exist)) + goto exit; + + /* + Preserve current role active role, in case this dump is imported + in the same connection that assumes the active role at the beginning + is the same as at the end of the connection. This is so: + + #!/bin/sh + ( + echo "set role special_role; "; + cat mysqldump.sql; + echo "$dosomethingspecial" + ) | mysql -h $host + + doesn't end up with a surprise that the $dosomethingspecial cannot + be done because `special_role` isn't active. + + We create a new role for importing that becomes the default admin for new + roles. This is because without being a admin on new roles we don't + have the necessary privileges to grant users to a created role or to + create new admins for the created role. + + At the end of the import the mariadb_dump_import_role is be dropped, + which implicitly drops all its admin aspects of the dropped role. + This is significantly easier than revoking the ADMIN of each role + from the current user. + */ + fputs("SELECT COALESCE(CURRENT_ROLE(),'NONE') into @current_role;\n" + "CREATE ROLE IF NOT EXISTS mariadb_dump_import_role;\n" + "GRANT mariadb_dump_import_role TO CURRENT_USER();\n" + "SET ROLE mariadb_dump_import_role;\n" + , md_result_file); + /* No show create role yet, MDEV-22311 */ + /* Roles, with user admins first, then roles they administer, and recurse on that */ + if (maria_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "WITH RECURSIVE create_role_order AS" + " (SELECT 1 as n, roles_mapping.*" + " FROM mysql.roles_mapping" + " JOIN mysql.user USING (user,host)" + " WHERE is_role='N'" + " AND Admin_option='Y'" + " UNION SELECT c.n+1, r.*" + " FROM create_role_order c" + " JOIN mysql.roles_mapping r ON c.role=r.user" + " AND r.host=''" + " AND r.Admin_option='Y') " + "SELECT QUOTE(ROLE) AS r," + " CONCAT(QUOTE(user)," + " IF(HOST='', '', CONCAT('@', QUOTE(HOST)))) AS c," + " Admin_option " + "FROM create_role_order ORDER BY n, r, user")) + return 1; + /* + TODO Mysql - misses roles that have no admin or role members. + MySQL roles don't require an admin. + */ + if (mysql_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "WITH RECURSIVE create_role_order AS" + " (SELECT 1 AS n," + " re.*" + " FROM mysql.role_edges re" + " JOIN mysql.user u ON re.TO_HOST=u.HOST" + " AND re.TO_USER = u.USER" + " LEFT JOIN mysql.role_edges re2 ON re.TO_USER=re2.FROM_USER" + " AND re2.TO_HOST=re2.FROM_HOST" + " WHERE re2.FROM_USER IS NULL" + " UNION SELECT c.n+1," + " re.*" + " FROM create_role_order c" + " JOIN mysql.role_edges re ON c.FROM_USER=re.TO_USER" + " AND c.FROM_HOST=re.TO_HOST) " + "SELECT CONCAT(QUOTE(FROM_USER), '/*!80001 @', QUOTE(FROM_HOST), '*/') AS r," + " CONCAT(QUOTE(TO_USER), IF(n=1, CONCAT('@', QUOTE(TO_HOST))," + " CONCAT('/*!80001 @', QUOTE(TO_HOST), ' */'))) AS u," + " WITH_ADMIN_OPTION " + "FROM create_role_order " + "ORDER BY n," + " FROM_USER," + " FROM_HOST," + " TO_USER," + " TO_HOST," + " WITH_ADMIN_OPTION")) + return 1; + while ((row= mysql_fetch_row(tableres))) + { + /* MySQL-8.0 export capability */ + if (opt_replace_into) + fprintf(md_result_file, + "/*!80001 DROP ROLE IF EXISTS %s */;\n", row[0]); + fprintf(md_result_file, + "/*!80001 CREATE ROLE %s%s */;\n", opt_ignore ? "IF NOT EXISTS " : "", row[0]); + /* By default created with current role */ + fprintf(md_result_file, + "%sROLE %s%s WITH ADMIN mariadb_dump_import_role */;\n", + opt_replace_into ? "/*M!100103 CREATE OR REPLACE ": "/*M!100005 CREATE ", + opt_ignore ? "IF NOT EXISTS " : "", row[0]); + fprintf(md_result_file, "/*M!100005 GRANT %s TO %s%s*/;\n", + row[0], row[1], (row[2][0] == 'Y') ? " WITH ADMIN OPTION " : ""); + } + mysql_free_result(tableres); + + /* users and their default role */ + if (maria_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "select IF(default_role='', 'NONE', QUOTE(default_role)) as r," + "concat(QUOTE(User), '@', QUOTE(Host)) as u FROM mysql.user " + "/*M!100005 WHERE is_role='N' */")) + return 1; + if (mysql_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "SELECT IF(DEFAULT_ROLE_HOST IS NULL, 'NONE', CONCAT(QUOTE(DEFAULT_ROLE_USER)," + " '@', QUOTE(DEFAULT_ROLE_HOST))) as r," + " CONCAT(QUOTE(mu.USER),'@',QUOTE(mu.HOST)) as u " + "FROM mysql.user mu LEFT JOIN mysql.default_roles using (USER, HOST)")) + { + mysql_free_result(tableres); + return 1; + } + + while ((row= mysql_fetch_row(tableres))) + { + if (dump_grants(row[1])) + result= 1; + fprintf(md_result_file, "/*M!100005 SET DEFAULT ROLE %s FOR %s */;\n", row[0], row[1]); + fprintf(md_result_file, "/*!80001 ALTER USER %s DEFAULT ROLE %s */;\n", row[1], row[0]); + } + mysql_free_result(tableres); + + if (maria_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "SELECT DISTINCT QUOTE(m.role) AS r " + " FROM mysql.roles_mapping m" + " JOIN mysql.user u ON u.user = m.role" + " WHERE is_role='Y'" + " AND Admin_option='Y'" + " ORDER BY m.role")) + return 1; + if (mysql_roles_exist && mysql_query_with_error_report(mysql, &tableres, + "SELECT DISTINCT CONCAT(QUOTE(FROM_USER),'@', QUOTE(FROM_HOST)) AS r " + "FROM mysql.role_edges")) + return 1; + while ((row= mysql_fetch_row(tableres))) + { + if (dump_grants(row[0])) + result= 1; + } + mysql_free_result(tableres); + /* switch back */ + fputs("SET ROLE NONE;\n" + "DROP ROLE mariadb_dump_import_role;\n" + "/*M!100203 EXECUTE IMMEDIATE CONCAT('SET ROLE ', @current_role) */;\n", + md_result_file); +exit: + + return result; +} + + +/* + dump all plugins +*/ + +static int dump_all_plugins() +{ + MYSQL_ROW row; + MYSQL_RES *tableres; + + if (mysql_query_with_error_report(mysql, &tableres, "SHOW PLUGINS")) + return 1; + /* Name, Status, Type, Library, License */ + while ((row= mysql_fetch_row(tableres))) + { + if (strcmp("ACTIVE", row[1]) != 0) + continue; + /* Should we be skipping builtins? */ + if (row[3] == NULL) + continue; + if (opt_replace_into) + { + fprintf(md_result_file, "/*M!100401 UNINSTALL PLUGIN IF EXIST %s */;\n", + row[0]); + } + fprintf(md_result_file, + "INSTALL PLUGIN %s %s SONAME '%s';\n", row[0], + opt_ignore ? "/*M!100401 IF NOT EXISTS */" : "", row[3]); + } + mysql_free_result(tableres); + + return 0; +} + + +/* + dump all udfs +*/ + +static int dump_all_udfs() +{ + /* we don't support all these types yet, but get prepared if we do */ + static const char *udf_types[] = {"STRING", "REAL", "INT", "ROW", "DECIMAL", "TIME" }; + MYSQL_ROW row; + MYSQL_RES *tableres; + int retresult, result= 0; + + if (mysql_query_with_error_report(mysql, &tableres, "SELECT * FROM mysql.func")) + return 1; + /* Name, ret, dl, type*/ + while ((row= mysql_fetch_row(tableres))) + { + retresult= atoi(row[1]); + if (retresult < 0 || array_elements(udf_types) <= (size_t) retresult) + { + fprintf(stderr, "%s: Error: invalid return type on udf function '%s'\n", + my_progname_short, row[0]); + result= 1; + continue; + } + if (opt_replace_into) + { + fprintf(md_result_file, "/*!50701 DROP FUNCTION IF EXISTS %s */;\n", + row[0]); + } + fprintf(md_result_file, + "CREATE %s%sFUNCTION %s%s RETURNS %s SONAME '%s';\n", + opt_replace_into ? "/*M!100103 OR REPLACE */ ": "", + (strcmp("AGGREGATE", row[2])==0 ? "AGGREGATE " : ""), + opt_ignore ? "IF NOT EXISTS " : "", row[0], udf_types[retresult], row[2]); + } + mysql_free_result(tableres); + + return result; +} + + +/* + dump all servers +*/ + +static int dump_all_servers() +{ + /* No create server yet - MDEV-15696 */ + MYSQL_ROW row; + MYSQL_RES *tableres; + MYSQL_FIELD *f; + unsigned int num_fields, i; + my_bool comma_prepend= 0; + const char *qstring; + + if (mysql_query_with_error_report(mysql, &tableres, "SELECT * FROM mysql.servers")) + return 1; + num_fields= mysql_num_fields(tableres); + while ((row= mysql_fetch_row(tableres))) + { + fprintf(md_result_file,"CREATE %sSERVER %s%s FOREIGN DATA WRAPPER %s OPTIONS (", + opt_replace_into ? "/*M!100103 OR REPLACE */ ": "", + opt_ignore ? "/*M!100103 IF NOT EXISTS */ " : "", row[0], row[7]); + for (i= 1; i < num_fields; i++) + { + if (i == 7 || row[i][0] == '\0') /* Wrapper or empty string */ + continue; + f= &tableres->fields[i]; + qstring= (f->type == MYSQL_TYPE_STRING || f->type == MYSQL_TYPE_VAR_STRING) ? "'" : ""; + fprintf(md_result_file, "%s%s %s%s%s", + (comma_prepend ? ", " : ""), f->name, qstring, row[i], qstring); + comma_prepend= 1; + } + fputs(");\n", md_result_file); + } + mysql_free_result(tableres); + + return 0; +} + + +/* + dump all system statistical tables +*/ + +static int dump_all_stats() +{ + my_bool prev_no_create_info, prev_opt_replace_into; + + if (mysql_select_db(mysql, "mysql")) + { + DB_error(mysql, "when selecting the database"); + return 1; /* If --force */ + } + fprintf(md_result_file,"\nUSE mysql;\n"); + prev_opt_replace_into= opt_replace_into; + opt_replace_into|= !opt_ignore; + prev_no_create_info= opt_no_create_info; + opt_no_create_info= 1; /* don't overwrite recreate tables */ + /* EITS added in 10.0.1 */ + if (mysql_get_server_version(mysql) >= 100001) + { + dump_table("column_stats", "mysql", NULL, 0); + dump_table("index_stats", "mysql", NULL, 0); + dump_table("table_stats", "mysql", NULL, 0); + } + /* Innodb may be disabled */ + if (!mysql_query(mysql, "show fields from innodb_index_stats")) + { + MYSQL_RES *tableres= mysql_store_result(mysql); + mysql_free_result(tableres); + dump_table("innodb_index_stats", "mysql", NULL, 0); + dump_table("innodb_table_stats", "mysql", NULL, 0); + } + opt_no_create_info= prev_no_create_info; + opt_replace_into= prev_opt_replace_into; + return 0; +} + + +/* + dump all system timezones +*/ + +static int dump_all_timezones() +{ + my_bool opt_prev_no_create_info, opt_prev_replace_into; + if (mysql_select_db(mysql, "mysql")) + { + DB_error(mysql, "when selecting the database"); + return 1; /* If --force */ + } + opt_prev_replace_into= opt_replace_into; + opt_replace_into|= !opt_ignore; + opt_prev_no_create_info= opt_no_create_info; + opt_no_create_info= 1; + fprintf(md_result_file,"\nUSE mysql;\n"); + dump_table("time_zone", "mysql", NULL, 0); + dump_table("time_zone_name", "mysql", NULL, 0); + dump_table("time_zone_leap_second", "mysql", NULL, 0); + dump_table("time_zone_transition", "mysql", NULL, 0); + dump_table("time_zone_transition_type", "mysql", NULL, 0); + opt_no_create_info= opt_prev_no_create_info; + opt_replace_into= opt_prev_replace_into; + return 0; +} + + +/* + dump all logfile groups and tablespaces +*/ + +static int dump_all_tablespaces() +{ + return dump_tablespaces(NULL); +} + +static int dump_tablespaces_for_tables(char *db, char **table_names, int tables) +{ + int r; + int i; + char name_buff[NAME_LEN*2+3]; + + mysql_real_escape_string(mysql, name_buff, db, (ulong)strlen(db)); + + init_dynamic_string_checked(&dynamic_where, " AND TABLESPACE_NAME IN (" + "SELECT DISTINCT TABLESPACE_NAME FROM" + " INFORMATION_SCHEMA.PARTITIONS" + " WHERE" + " TABLE_SCHEMA='", 256, 1024); + dynstr_append_checked(&dynamic_where, name_buff); + dynstr_append_checked(&dynamic_where, "' AND TABLE_NAME IN ("); + + for (i=0 ; i= FIRST_INFORMATION_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], INFORMATION_SCHEMA_DB_NAME)) + continue; + + if (mysql_get_server_version(mysql) >= FIRST_PERFORMANCE_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], PERFORMANCE_SCHEMA_DB_NAME)) + continue; + + if (mysql_get_server_version(mysql) >= FIRST_SYS_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], SYS_SCHEMA_DB_NAME)) + continue; + + if (include_database(row[0])) + if (dump_all_tables_in_db(row[0])) + result=1; + } + mysql_free_result(tableres); + if (seen_views) + { + if (mysql_query(mysql, "SHOW DATABASES") || + !(tableres= mysql_store_result(mysql))) + { + fprintf(stderr, "%s: Error: Couldn't execute 'SHOW DATABASES': %s\n", + my_progname_short, mysql_error(mysql)); + return 1; + } + while ((row= mysql_fetch_row(tableres))) + { + if (mysql_get_server_version(mysql) >= FIRST_INFORMATION_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], INFORMATION_SCHEMA_DB_NAME)) + continue; + + if (mysql_get_server_version(mysql) >= FIRST_PERFORMANCE_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], PERFORMANCE_SCHEMA_DB_NAME)) + continue; + + if (mysql_get_server_version(mysql) >= FIRST_SYS_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, row[0], SYS_SCHEMA_DB_NAME)) + continue; + + if (include_database(row[0])) + if (dump_all_views_in_db(row[0])) + result=1; + } + mysql_free_result(tableres); + } + return result; +} +/* dump_all_databases */ + + +static int dump_databases(char **db_names) +{ + int result=0; + char **db; + DBUG_ENTER("dump_databases"); + + for (db= db_names ; *db ; db++) + { + if (dump_all_tables_in_db(*db)) + result=1; + } + if (!result && seen_views) + { + for (db= db_names ; *db ; db++) + { + if (dump_all_views_in_db(*db)) + result=1; + } + } + DBUG_RETURN(result); +} /* dump_databases */ + + +/* +View Specific database initialization. + +SYNOPSIS + init_dumping_views + qdatabase quoted name of the database + +RETURN VALUES + 0 Success. + 1 Failure. +*/ +int init_dumping_views(char *qdatabase __attribute__((unused))) +{ + return 0; +} /* init_dumping_views */ + + +/* +mysql specific database initialization. + +SYNOPSIS + init_dumping_mysql_tables + + protections around dumping general/slow query log + qdatabase quoted name of the "mysql" database + +RETURN VALUES + 0 Success. + 1 Failure. +*/ +static int init_dumping_mysql_tables(char *qdatabase) +{ + DBUG_ENTER("init_dumping_mysql_tables"); + + if (opt_drop_database) + fprintf(md_result_file, + "\n/*!50106 SET @save_log_output=@@LOG_OUTPUT*/;\n" + "/*M!100203 EXECUTE IMMEDIATE IF(@@LOG_OUTPUT='TABLE' AND (@@LOG_SLOW_QUERY=1 OR @@GENERAL_LOG=1)," + "\"SET GLOBAL LOG_OUTPUT='NONE'\", \"DO 0\") */;\n"); + + DBUG_RETURN(init_dumping_tables(qdatabase)); +} + + +static void dump_first_mysql_tables(char *database) +{ + char table_type[NAME_LEN]; + char ignore_flag; + DBUG_ENTER("dump_first_mysql_tables"); + + if (!get_table_structure((char *) "general_log", + database, table_type, &ignore_flag, NULL) ) + verbose_msg("-- Warning: get_table_structure() failed with some internal " + "error for 'general_log' table\n"); + if (!get_table_structure((char *) "slow_log", + database, table_type, &ignore_flag, NULL) ) + verbose_msg("-- Warning: get_table_structure() failed with some internal " + "error for 'slow_log' table\n"); + /* general and slow query logs exist now */ + if (opt_drop_database) + fprintf(md_result_file, + "\n/*!50106 SET GLOBAL LOG_OUTPUT=@save_log_output*/;\n\n"); + DBUG_VOID_RETURN; +} + + +/* +Table Specific database initialization. + +SYNOPSIS + init_dumping_tables + qdatabase quoted name of the database + +RETURN VALUES + 0 Success. + 1 Failure. +*/ + +int init_dumping_tables(char *qdatabase) +{ + DBUG_ENTER("init_dumping_tables"); + + if (!opt_create_db) + { + char qbuf[256]; + MYSQL_ROW row; + MYSQL_RES *dbinfo; + + my_snprintf(qbuf, sizeof(qbuf), + "SHOW CREATE DATABASE IF NOT EXISTS %s", + qdatabase); + + if (mysql_query(mysql, qbuf) || !(dbinfo = mysql_store_result(mysql))) + { + /* Old server version, dump generic CREATE DATABASE */ + if (opt_drop_database) + fprintf(md_result_file, + "\n/*!40000 DROP DATABASE IF EXISTS %s*/;\n", + qdatabase); + fprintf(md_result_file, + "\nCREATE DATABASE /*!32312 IF NOT EXISTS*/ %s;\n", + qdatabase); + } + else + { + if (opt_drop_database) + fprintf(md_result_file, + "\n/*!40000 DROP DATABASE IF EXISTS %s*/;\n", + qdatabase); + row = mysql_fetch_row(dbinfo); + if (row[1]) + { + fprintf(md_result_file,"\n%s;\n",row[1]); + } + mysql_free_result(dbinfo); + } + } + DBUG_RETURN(0); +} /* init_dumping_tables */ + + +static int init_dumping(char *database, int init_func(char*)) +{ + if (mysql_select_db(mysql, database)) + { + DB_error(mysql, "when selecting the database"); + return 1; /* If --force */ + } + if (!path && !opt_xml) + { + if (opt_databases || opt_alldbs) + { + /* + length of table name * 2 (if name contains quotes), 2 quotes and 0 + */ + char quoted_database_buf[NAME_LEN*2+3]; + char *qdatabase= quote_name(database,quoted_database_buf,opt_quoted); + + print_comment(md_result_file, 0, + "\n--\n-- Current Database: %s\n--\n", + fix_for_comment(qdatabase)); + + /* Call the view or table specific function */ + init_func(qdatabase); + + fprintf(md_result_file,"\nUSE %s;\n", qdatabase); + check_io(md_result_file); + } + } + return 0; +} /* init_dumping */ + + +/* Return 1 if we should copy the table */ + +static my_bool include_table(const uchar *hash_key, size_t len) +{ + return ! my_hash_search(&ignore_table, hash_key, len); +} +static my_bool ignore_table_data(const uchar *hash_key, size_t len) +{ + return my_hash_search(&ignore_data, hash_key, len) != NULL; +} + + +static int dump_all_tables_in_db(char *database) +{ + char *table; + uint numrows; + char table_buff[NAME_LEN*2+3]; + char hash_key[2*NAME_LEN+2]; /* "db.tablename" */ + char *afterdot; + my_bool transaction_registry_table_exists= 0; + int using_mysql_db= !my_strcasecmp(charset_info, database, "mysql"); + DBUG_ENTER("dump_all_tables_in_db"); + + afterdot= strmov(hash_key, database); + *afterdot++= '.'; + + if (init_dumping(database, using_mysql_db ? init_dumping_mysql_tables + : init_dumping_tables)) + DBUG_RETURN(1); + if (opt_xml) + print_xml_tag(md_result_file, "", "\n", "database", "name=", database, NullS); + + if (using_mysql_db) + dump_first_mysql_tables(database); + + if (lock_tables) + { + DYNAMIC_STRING query; + init_dynamic_string_checked(&query, "LOCK TABLES ", 256, 1024); + for (numrows= 0 ; (table= getTableName(1, DUMP_TABLE_ALL)) ; ) + { + char *end= strmov(afterdot, table); + if (include_table((uchar*) hash_key,end - hash_key)) + { + numrows++; + dynstr_append_checked(&query, quote_name(table, table_buff, 1)); + dynstr_append_checked(&query, " READ /*!32311 LOCAL */,"); + } + } + if (numrows && mysql_real_query(mysql, query.str, (ulong)query.length-1)) + { + dynstr_free(&query); + DB_error(mysql, "when using LOCK TABLES"); + /* We shall continue here, if --force was given */ + } + dynstr_free(&query); /* Safe to call twice */ + } + if (flush_logs) + { + if (mysql_refresh(mysql, REFRESH_LOG)) + DB_error(mysql, "when doing refresh"); + /* We shall continue here, if --force was given */ + else + verbose_msg("-- dump_all_tables_in_db : logs flushed successfully!\n"); + } + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Setting savepoint...\n"); + if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp")) + { + DBUG_RETURN(1); + } + } + + if (mysql_get_server_version(mysql) >= FIRST_SEQUENCE_VERSION && + !opt_no_create_info) + { + // First process sequences + while ((table= getTableName(1, DUMP_TABLE_SEQUENCE))) + { + char *end= strmov(afterdot, table); + if (include_table((uchar*) hash_key, end - hash_key)) + get_sequence_structure(table, database); + } + } + while ((table= getTableName(0, DUMP_TABLE_TABLE))) + { + char *end= strmov(afterdot, table); + if (include_table((uchar*) hash_key, end - hash_key)) + { + dump_table(table, database, (uchar*) hash_key, end - hash_key); + my_free(order_by); + order_by= 0; + if (opt_dump_triggers && mysql_get_server_version(mysql) >= 50009) + { + if (dump_triggers_for_table(table, database)) + { + if (path) + my_fclose(md_result_file, MYF(MY_WME)); + maybe_exit(EX_MYSQLERR); + } + } + + /** + ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata + lock on table which was already dumped. This allows to avoid blocking + concurrent DDL on this table without sacrificing correctness, as we + won't access table second time and dumps created by --single-transaction + mode have validity point at the start of transaction anyway. + Note that this doesn't make --single-transaction mode with concurrent + DDL safe in general case. It just improves situation for people for whom + it might be working. + */ + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Rolling back to savepoint sp...\n"); + if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp")) + maybe_exit(EX_MYSQLERR); + } + } + else + { + /* + If transaction_registry exists in the 'mysql' database, + we should dump the table structure. But we cannot + call get_table_structure() here as 'LOCK TABLES' query got executed + above on the session and that 'LOCK TABLES' query does not contain + 'transaction_registry'. Hence mark the existence of the table here and + after 'UNLOCK TABLES' query is executed on the session, get the table + structure from server and dump it in the file. + */ + if (using_mysql_db && !my_strcasecmp(charset_info, table, "transaction_registry")) + transaction_registry_table_exists= 1; + } + } + + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Releasing savepoint...\n"); + if (mysql_query_with_error_report(mysql, 0, "RELEASE SAVEPOINT sp")) + DBUG_RETURN(1); + } + + if (opt_events && mysql_get_server_version(mysql) >= 50106) + { + DBUG_PRINT("info", ("Dumping events for database %s", database)); + dump_events_for_db(database); + } + if (opt_routines && mysql_get_server_version(mysql) >= 50009) + { + DBUG_PRINT("info", ("Dumping routines for database %s", database)); + dump_routines_for_db(database); + } + if (lock_tables) + (void) mysql_query_with_error_report(mysql, 0, "UNLOCK TABLES"); + if (using_mysql_db) + { + if (transaction_registry_table_exists) + { + char table_type[NAME_LEN]; + char ignore_flag; + if (!get_table_structure((char *) "transaction_registry", + database, table_type, &ignore_flag, NULL) ) + verbose_msg("-- Warning: get_table_structure() failed with some internal " + "error for 'transaction_registry' table\n"); + } + } + if (opt_xml) + { + fputs("\n", md_result_file); + check_io(md_result_file); + } + if (flush_privileges && using_mysql_db) + { + fprintf(md_result_file,"\n--\n-- Flush Grant Tables \n--\n"); + fprintf(md_result_file,"\n/*! FLUSH PRIVILEGES */;\n"); + } + DBUG_RETURN(0); +} /* dump_all_tables_in_db */ + + +/* + dump structure of views of database + + SYNOPSIS + dump_all_views_in_db() + database database name + + RETURN + 0 OK + 1 ERROR +*/ + +static my_bool dump_all_views_in_db(char *database) +{ + char *table; + uint numrows; + char table_buff[NAME_LEN*2+3]; + char hash_key[2*NAME_LEN+2]; /* "db.tablename" */ + char *afterdot; + + afterdot= strmov(hash_key, database); + *afterdot++= '.'; + + if (init_dumping(database, init_dumping_views)) + return 1; + if (opt_xml) + print_xml_tag(md_result_file, "", "\n", "database", "name=", database, NullS); + if (lock_tables) + { + DYNAMIC_STRING query; + init_dynamic_string_checked(&query, "LOCK TABLES ", 256, 1024); + for (numrows= 0 ; (table= getTableName(1, DUMP_TABLE_TABLE)); ) + { + char *end= strmov(afterdot, table); + if (include_table((uchar*) hash_key,end - hash_key)) + { + numrows++; + dynstr_append_checked(&query, quote_name(table, table_buff, 1)); + dynstr_append_checked(&query, " READ /*!32311 LOCAL */,"); + } + } + if (numrows && mysql_real_query(mysql, query.str, (ulong)query.length-1)) + DB_error(mysql, "when using LOCK TABLES"); + /* We shall continue here, if --force was given */ + dynstr_free(&query); + } + if (flush_logs) + { + if (mysql_refresh(mysql, REFRESH_LOG)) + DB_error(mysql, "when doing refresh"); + /* We shall continue here, if --force was given */ + else + verbose_msg("-- dump_all_views_in_db : logs flushed successfully!\n"); + } + while ((table= getTableName(0, DUMP_TABLE_TABLE))) + { + char *end= strmov(afterdot, table); + if (include_table((uchar*) hash_key, end - hash_key)) + get_view_structure(table, database); + } + if (opt_xml) + { + fputs("\n", md_result_file); + check_io(md_result_file); + } + if (lock_tables) + (void) mysql_query_with_error_report(mysql, 0, "UNLOCK TABLES"); + return 0; +} /* dump_all_tables_in_db */ + + +/* + See get_actual_table_name. Used to retrieve the correct table name + from the database schema. +*/ +static char *get_actual_table_name_helper(const char *old_table_name, + my_bool case_sensitive, + MEM_ROOT *root) +{ + char *name= 0; + MYSQL_RES *table_res; + MYSQL_ROW row; + char query[50 + 2*NAME_LEN]; + char show_name_buff[FN_REFLEN]; + DBUG_ENTER("get_actual_table_name_helper"); + + /* Check memory for quote_for_like() */ + DBUG_ASSERT(2*sizeof(old_table_name) < sizeof(show_name_buff)); + + if (case_sensitive) + { + DBUG_PRINT("info", ("case sensitive search")); + my_snprintf(query, sizeof(query), + "SELECT table_name FROM INFORMATION_SCHEMA.TABLES " + "WHERE table_schema = DATABASE() AND table_name = %s", + quote_for_equal(old_table_name, show_name_buff)); + } + else + { + DBUG_PRINT("info", ("case insensitive search")); + my_snprintf(query, sizeof(query), "SHOW TABLES LIKE %s", + quote_for_like(old_table_name, show_name_buff)); + } + + if (mysql_query_with_error_report(mysql, 0, query)) + return NullS; + + if ((table_res= mysql_store_result(mysql))) + { + my_ulonglong num_rows= mysql_num_rows(table_res); + if (num_rows > 0) + { + ulong *lengths; + /* + Return first row + TODO: Return all matching rows + */ + row= mysql_fetch_row(table_res); + lengths= mysql_fetch_lengths(table_res); + name= strmake_root(root, row[0], lengths[0]); + } + mysql_free_result(table_res); + } + DBUG_PRINT("exit", ("new_table_name: %s", name)); + DBUG_RETURN(name); +} + +/* + get_actual_table_name -- executes a SELECT .. FROM I_S.tables to check + if the table name given on the command line matches the one in the database. + If the table is not found, it falls back to a slower SHOW TABLES LIKE '%s' to + get the actual table name from the server. + + We do this because the table name given on the command line may be a + different case (e.g. T1 vs t1), but checking this takes a long time + when there are many tables present. + + RETURN + pointer to the table name + 0 if error +*/ + +static char *get_actual_table_name(const char *old_table_name, + int lower_case_table_names, + MEM_ROOT *root) +{ + char *name= 0; + DBUG_ENTER("get_actual_table_name"); + + name= get_actual_table_name_helper(old_table_name, TRUE, root); + if (!name && !lower_case_table_names) + name= get_actual_table_name_helper(old_table_name, FALSE, root); + DBUG_RETURN(name); +} + +/* + Retrieve the value for the server system variable lower_case_table_names. + + RETURN + 0 case sensitive. + > 0 case insensitive +*/ +static int get_sys_var_lower_case_table_names() +{ + int lower_case_table_names = 0; + MYSQL_RES *table_res; + MYSQL_ROW row; + const char *show_var_query = "SHOW VARIABLES LIKE 'lower_case_table_names'"; + if (mysql_query_with_error_report(mysql, &table_res, show_var_query)) + return 0; /* In case of error, assume default value of 0 */ + + if ((row= mysql_fetch_row(table_res))) + { + lower_case_table_names= atoi(row[1]); + mysql_free_result(table_res); + } + if (!row) + mysql_free_result(table_res); + return lower_case_table_names; +} + + + +static int dump_selected_tables(char *db, char **table_names, int tables) +{ + char table_buff[NAME_LEN*2+3], table_type[NAME_LEN]; + DYNAMIC_STRING lock_tables_query; + char **dump_tables, **pos, **end; + int lower_case_table_names; + DBUG_ENTER("dump_selected_tables"); + + if (init_dumping(db, init_dumping_tables)) + DBUG_RETURN(1); + + init_alloc_root(PSI_NOT_INSTRUMENTED, &glob_root, 8192, 0, MYF(0)); + if (!(dump_tables= pos= (char**) alloc_root(&glob_root, + tables * sizeof(char *)))) + die(EX_EOM, "alloc_root failure."); + + /* Figure out how to compare table names. */ + lower_case_table_names = get_sys_var_lower_case_table_names(); + + init_dynamic_string_checked(&lock_tables_query, "LOCK TABLES ", 256, 1024); + for (; tables > 0 ; tables-- , table_names++) + { + /* the table name passed on commandline may be wrong case */ + if ((*pos= get_actual_table_name(*table_names, lower_case_table_names, + &glob_root))) + { + /* Add found table name to lock_tables_query */ + if (lock_tables) + { + dynstr_append_checked(&lock_tables_query, quote_name(*pos, table_buff, 1)); + dynstr_append_checked(&lock_tables_query, " READ /*!32311 LOCAL */,"); + } + pos++; + } + else + { + if (!ignore_errors) + { + dynstr_free(&lock_tables_query); + free_root(&glob_root, MYF(0)); + } + maybe_die(EX_ILLEGAL_TABLE, "Couldn't find table: \"%s\"", *table_names); + /* We shall countinue here, if --force was given */ + } + } + end= pos; + + /* Can't LOCK TABLES in I_S / P_S, so don't try. */ + if (lock_tables && + !(mysql_get_server_version(mysql) >= FIRST_INFORMATION_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, db, INFORMATION_SCHEMA_DB_NAME)) && + !(mysql_get_server_version(mysql) >= FIRST_PERFORMANCE_SCHEMA_VERSION && + !my_strcasecmp(&my_charset_latin1, db, PERFORMANCE_SCHEMA_DB_NAME))) + { + if (mysql_real_query(mysql, lock_tables_query.str, + (ulong)lock_tables_query.length-1)) + { + if (!ignore_errors) + { + dynstr_free(&lock_tables_query); + free_root(&glob_root, MYF(0)); + } + DB_error(mysql, "when doing LOCK TABLES"); + /* We shall countinue here, if --force was given */ + } + } + dynstr_free(&lock_tables_query); + if (flush_logs) + { + if (mysql_refresh(mysql, REFRESH_LOG)) + { + if (!ignore_errors) + free_root(&glob_root, MYF(0)); + DB_error(mysql, "when doing refresh"); + } + /* We shall countinue here, if --force was given */ + else + verbose_msg("-- dump_selected_tables : logs flushed successfully!\n"); + } + if (opt_xml) + print_xml_tag(md_result_file, "", "\n", "database", "name=", db, NullS); + + + /* obtain dump of routines (procs/functions) */ + if (opt_routines && mysql_get_server_version(mysql) >= 50009) + { + DBUG_PRINT("info", ("Dumping routines for database %s", db)); + dump_routines_for_db(db); + } + + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Setting savepoint...\n"); + if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp")) + { + free_root(&glob_root, MYF(0)); + DBUG_RETURN(1); + } + } + + if (mysql_get_server_version(mysql) >= FIRST_SEQUENCE_VERSION) + { + /* Dump Sequence first */ + for (pos= dump_tables; pos < end; pos++) + { + DBUG_PRINT("info",("Dumping sequence(?) %s", *pos)); + if (check_if_ignore_table(*pos, table_type) & IGNORE_SEQUENCE_TABLE) + get_sequence_structure(*pos, db); + } + } + /* Dump each selected table */ + for (pos= dump_tables; pos < end; pos++) + { + if (check_if_ignore_table(*pos, table_type) & IGNORE_SEQUENCE_TABLE) + continue; + DBUG_PRINT("info",("Dumping table %s", *pos)); + dump_table(*pos, db, NULL, 0); + if (opt_dump_triggers && + mysql_get_server_version(mysql) >= 50009) + { + if (dump_triggers_for_table(*pos, db)) + { + if (path) + my_fclose(md_result_file, MYF(MY_WME)); + if (!ignore_errors) + free_root(&glob_root, MYF(0)); + maybe_exit(EX_MYSQLERR); + } + } + + /** + ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata + lock on table which was already dumped. This allows to avoid blocking + concurrent DDL on this table without sacrificing correctness, as we + won't access table second time and dumps created by --single-transaction + mode have validity point at the start of transaction anyway. + Note that this doesn't make --single-transaction mode with concurrent + DDL safe in general case. It just improves situation for people for whom + it might be working. + */ + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Rolling back to savepoint sp...\n"); + if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp")) + { + if (!ignore_errors) + free_root(&glob_root, MYF(0)); + maybe_exit(EX_MYSQLERR); + } + } + } + + if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) + { + verbose_msg("-- Releasing savepoint...\n"); + if (mysql_query_with_error_report(mysql, 0, "RELEASE SAVEPOINT sp")) + { + free_root(&glob_root, MYF(0)); + DBUG_RETURN(1); + } + } + + /* Dump each selected view */ + if (seen_views) + { + for (pos= dump_tables; pos < end; pos++) + get_view_structure(*pos, db); + } + if (opt_events && mysql_get_server_version(mysql) >= 50106) + { + DBUG_PRINT("info", ("Dumping events for database %s", db)); + dump_events_for_db(db); + } + free_root(&glob_root, MYF(0)); + if (opt_xml) + { + fputs("\n", md_result_file); + check_io(md_result_file); + } + if (lock_tables) + (void) mysql_query_with_error_report(mysql, 0, "UNLOCK TABLES"); + DBUG_RETURN(0); +} /* dump_selected_tables */ + + +static int do_show_master_status(MYSQL *mysql_con, int consistent_binlog_pos, + int have_mariadb_gtid, int use_gtid) +{ + MYSQL_ROW row; + MYSQL_RES *UNINIT_VAR(master); + char binlog_pos_file[FN_REFLEN]; + char binlog_pos_offset[LONGLONG_LEN+1]; + char gtid_pos[MAX_GTID_LENGTH]; + char *file, *offset; + const char *comment_prefix= + (opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : ""; + + if (consistent_binlog_pos) + { + if(!check_consistent_binlog_pos(binlog_pos_file, binlog_pos_offset)) + return 1; + file= binlog_pos_file; + offset= binlog_pos_offset; + if (have_mariadb_gtid && + get_binlog_gtid_pos(binlog_pos_file, binlog_pos_offset, gtid_pos)) + return 1; + } + else + { + if (mysql_query_with_error_report(mysql_con, &master, + "SHOW MASTER STATUS")) + return 1; + + row= mysql_fetch_row(master); + if (row && row[0] && row[1]) + { + file= row[0]; + offset= row[1]; + } + else + { + mysql_free_result(master); + if (!ignore_errors) + { + /* SHOW MASTER STATUS reports nothing and --force is not enabled */ + fprintf(stderr, "%s: Error: Binlogging on server not active\n", + my_progname_short); + maybe_exit(EX_MYSQLERR); + return 1; + } + else + { + return 0; + } + } + + if (have_mariadb_gtid && get_gtid_pos(gtid_pos, 1)) + { + mysql_free_result(master); + return 1; + } + + } + + /* SHOW MASTER STATUS reports file and position */ + print_comment(md_result_file, 0, + "\n--\n-- Position to start replication or point-in-time " + "recovery from\n--\n\n"); + fprintf(md_result_file, + "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", + (use_gtid ? "-- " : comment_prefix), file, offset); + if (have_mariadb_gtid) + { + print_comment(md_result_file, 0, + "\n--\n-- GTID to start replication from\n--\n\n"); + if (use_gtid) + fprintf(md_result_file, + "%sCHANGE MASTER TO MASTER_USE_GTID=slave_pos;\n", + comment_prefix); + fprintf(md_result_file, + "%sSET GLOBAL gtid_slave_pos='%s';\n", + (!use_gtid ? "-- " : comment_prefix), gtid_pos); + } + check_io(md_result_file); + + if (!consistent_binlog_pos) + mysql_free_result(master); + + return 0; +} + +static int do_stop_slave_sql(MYSQL *mysql_con) +{ + MYSQL_RES *slave; + MYSQL_ROW row; + + if (mysql_query_with_error_report(mysql_con, &slave, + multi_source ? + "SHOW ALL SLAVES STATUS" : + "SHOW SLAVE STATUS")) + return(1); + + /* Loop over all slaves */ + while ((row= mysql_fetch_row(slave))) + { + if (row[11 + multi_source]) + { + /* if SLAVE SQL is not running, we don't stop it */ + if (strcmp(row[11 + multi_source], "No")) + { + char query[160]; + if (multi_source) + sprintf(query, "STOP SLAVE '%.80s' SQL_THREAD", row[0]); + else + strmov(query, "STOP SLAVE SQL_THREAD"); + + if (mysql_query_with_error_report(mysql_con, 0, query)) + { + mysql_free_result(slave); + return 1; + } + } + } + } + mysql_free_result(slave); + return(0); +} + +static int add_stop_slave(void) +{ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- stop slave statement to make a recovery dump)\n--\n\n"); + if (multi_source) + fprintf(md_result_file, "STOP ALL SLAVES;\n"); + else + fprintf(md_result_file, "STOP SLAVE;\n"); + return(0); +} + +static int add_slave_statements(void) +{ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- start slave statement to make a recovery dump)\n--\n\n"); + if (multi_source) + fprintf(md_result_file, "START ALL SLAVES;\n"); + else + fprintf(md_result_file, "START SLAVE;\n"); + return(0); +} + +static int do_show_slave_status(MYSQL *mysql_con, int use_gtid, + int have_mariadb_gtid) +{ + MYSQL_RES *UNINIT_VAR(slave); + MYSQL_ROW row; + const char *comment_prefix= + (opt_slave_data == MYSQL_OPT_SLAVE_DATA_COMMENTED_SQL) ? "-- " : ""; + const char *gtid_comment_prefix= (use_gtid ? comment_prefix : "-- "); + const char *nogtid_comment_prefix= (!use_gtid ? comment_prefix : "-- "); + int set_gtid_done= 0; + + if (mysql_query_with_error_report(mysql_con, &slave, + multi_source ? + "SHOW ALL SLAVES STATUS" : + "SHOW SLAVE STATUS")) + { + if (!ignore_errors) + { + /* SHOW SLAVE STATUS reports nothing and --force is not enabled */ + fprintf(stderr, "%s: Error: Slave not set up\n", my_progname_short); + } + mysql_free_result(slave); + return 1; + } + + while ((row= mysql_fetch_row(slave))) + { + if (multi_source && !set_gtid_done) + { + char gtid_pos[MAX_GTID_LENGTH]; + if (have_mariadb_gtid && get_gtid_pos(gtid_pos, 0)) + { + mysql_free_result(slave); + return 1; + } + if (opt_comments) + fprintf(md_result_file, "\n--\n-- Gtid position to start replication " + "from\n--\n\n"); + fprintf(md_result_file, "%sSET GLOBAL gtid_slave_pos='%s';\n", + gtid_comment_prefix, gtid_pos); + set_gtid_done= 1; + } + if (row[9 + multi_source] && row[21 + multi_source]) + { + if (use_gtid) + { + if (multi_source) + fprintf(md_result_file, "%sCHANGE MASTER '%.80s' TO " + "MASTER_USE_GTID=slave_pos;\n", gtid_comment_prefix, row[0]); + else + fprintf(md_result_file, "%sCHANGE MASTER TO " + "MASTER_USE_GTID=slave_pos;\n", gtid_comment_prefix); + } + + /* SHOW MASTER STATUS reports file and position */ + if (opt_comments) + fprintf(md_result_file, + "\n--\n-- Position to start replication or point-in-time " + "recovery from (the master of this slave)\n--\n\n"); + + if (multi_source) + fprintf(md_result_file, "%sCHANGE MASTER '%.80s' TO ", + nogtid_comment_prefix, row[0]); + else + fprintf(md_result_file, "%sCHANGE MASTER TO ", nogtid_comment_prefix); + + if (opt_include_master_host_port) + { + if (row[1 + multi_source]) + fprintf(md_result_file, "MASTER_HOST='%s', ", row[1 + multi_source]); + if (row[3]) + fprintf(md_result_file, "MASTER_PORT=%s, ", row[3 + multi_source]); + } + fprintf(md_result_file, + "MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", + row[9 + multi_source], row[21 + multi_source]); + + check_io(md_result_file); + } + } + mysql_free_result(slave); + return 0; +} + +static int do_start_slave_sql(MYSQL *mysql_con) +{ + MYSQL_RES *slave; + MYSQL_ROW row; + int error= 0; + DBUG_ENTER("do_start_slave_sql"); + + /* We need to check if the slave sql is stopped in the first place */ + if (mysql_query_with_error_report(mysql_con, &slave, + multi_source ? + "SHOW ALL SLAVES STATUS" : + "SHOW SLAVE STATUS")) + DBUG_RETURN(1); + + while ((row= mysql_fetch_row(slave))) + { + DBUG_PRINT("info", ("Connection: '%s' status: '%s'", + multi_source ? row[0] : "", row[11 + multi_source])); + if (row[11 + multi_source]) + { + /* if SLAVE SQL is not running, we don't start it */ + if (strcmp(row[11 + multi_source], "Yes")) + { + char query[160]; + if (multi_source) + sprintf(query, "START SLAVE '%.80s'", row[0]); + else + strmov(query, "START SLAVE"); + + if (mysql_query_with_error_report(mysql_con, 0, query)) + { + fprintf(stderr, "%s: Error: Unable to start slave '%s'\n", + my_progname_short, multi_source ? row[0] : ""); + error= 1; + } + } + } + } + mysql_free_result(slave); + DBUG_RETURN(error); +} + + + +static int do_flush_tables_read_lock(MYSQL *mysql_con) +{ + /* + We do first a FLUSH TABLES. If a long update is running, the FLUSH TABLES + will wait but will not stall the whole mysqld, and when the long update is + done the FLUSH TABLES WITH READ LOCK will start and succeed quickly. So, + FLUSH TABLES is to lower the probability of a stage where both mysqldump + and most client connections are stalled. Of course, if a second long + update starts between the two FLUSHes, we have that bad stall. + + We use the LOCAL option, as we do not want the FLUSH TABLES replicated to + other servers. + */ + return + ( mysql_query_with_error_report(mysql_con, 0, + "FLUSH /*!40101 LOCAL */ TABLES") || + mysql_query_with_error_report(mysql_con, 0, + "FLUSH TABLES WITH READ LOCK") ); +} + + +static int do_unlock_tables(MYSQL *mysql_con) +{ + return mysql_query_with_error_report(mysql_con, 0, "UNLOCK TABLES"); +} + +static int get_bin_log_name(MYSQL *mysql_con, + char* buff_log_name, uint buff_len) +{ + MYSQL_RES *res; + MYSQL_ROW row; + + if (mysql_query(mysql_con, "SHOW MASTER STATUS") || + !(res= mysql_store_result(mysql))) + return 1; + + if (!(row= mysql_fetch_row(res))) + { + mysql_free_result(res); + return 1; + } + /* + Only one row is returned, and the first column is the name of the + active log. + */ + strmake(buff_log_name, row[0], buff_len - 1); + + mysql_free_result(res); + return 0; +} + +static int purge_bin_logs_to(MYSQL *mysql_con, char* log_name) +{ + DYNAMIC_STRING str; + int err; + init_dynamic_string_checked(&str, "PURGE BINARY LOGS TO '", 1024, 1024); + dynstr_append_checked(&str, log_name); + dynstr_append_checked(&str, "'"); + err = mysql_query_with_error_report(mysql_con, 0, str.str); + dynstr_free(&str); + return err; +} + + +static int start_transaction(MYSQL *mysql_con) +{ + verbose_msg("-- Starting transaction...\n"); + /* + We use BEGIN for old servers. --single-transaction --master-data will fail + on old servers, but that's ok as it was already silently broken (it didn't + do a consistent read, so better tell people frankly, with the error). + + We want the first consistent read to be used for all tables to dump so we + need the REPEATABLE READ level (not anything lower, for example READ + COMMITTED would give one new consistent read per dumped table). + */ + if ((mysql_get_server_version(mysql_con) < 40100) && opt_master_data) + { + fprintf(stderr, "-- %s: the combination of --single-transaction and " + "--master-data requires a MariaDB server version of at least 4.1 " + "(current server's version is %s). %s\n", + ignore_errors ? "Warning" : "Error", + mysql_con->server_version ? mysql_con->server_version : "unknown", + ignore_errors ? "Continuing due to --force, backup may not be consistent across all tables!" : "Aborting."); + if (!ignore_errors) + exit(EX_MYSQLERR); + } + + return (mysql_query_with_error_report(mysql_con, 0, + "SET SESSION TRANSACTION ISOLATION " + "LEVEL REPEATABLE READ") || + mysql_query_with_error_report(mysql_con, 0, + "START TRANSACTION " + "/*!40100 WITH CONSISTENT SNAPSHOT */")); +} + + +static ulong find_set(TYPELIB *lib, const char *x, size_t length, + char **err_pos, uint *err_len) +{ + const char *end= x + length; + ulong found= 0; + int find; + char buff[255]; + + *err_pos= 0; /* No error yet */ + while (end > x && my_isspace(charset_info, end[-1])) + end--; + + *err_len= 0; + if (x != end) + { + const char *start= x; + for (;;) + { + const char *pos= start; + uint var_len; + + for (; pos != end && *pos != ','; pos++) ; + var_len= (uint) (pos - start); + strmake(buff, start, MY_MIN(sizeof(buff) - 1, var_len)); + find= find_type(buff, lib, FIND_TYPE_BASIC); + if (find <= 0) + { + *err_pos= (char*) start; + *err_len= var_len; + } + else + found|= 1UL << (find - 1); + if (pos == end) + break; + start= pos + 1; + } + } + return found; +} + + +/* Print a value with a prefix on file */ +static void print_value(FILE *file, MYSQL_RES *result, MYSQL_ROW row, + const char *prefix, const char *name, + int string_value) +{ + MYSQL_FIELD *field; + mysql_field_seek(result, 0); + + for ( ; (field= mysql_fetch_field(result)) ; row++) + { + if (!strcmp(field->name,name)) + { + if (row[0] && row[0][0] && strcmp(row[0],"0")) /* Skip default */ + { + fputc(' ',file); + fputs(prefix, file); + if (string_value) + unescape(file,row[0], strlen(row[0])); + else + fputs(row[0], file); + check_io(file); + return; + } + } + } + return; /* This shouldn't happen */ +} /* print_value */ + + +/* + SYNOPSIS + + Check if we the table is one of the table types that should be ignored: + MRG_ISAM, MRG_MYISAM, if opt_delayed, if that table supports delayed inserts. + If the table should be altogether ignored, it returns a TRUE, FALSE if it + should not be ignored. If the user has selected to use INSERT DELAYED, it + sets the value of the bool pointer supports_delayed_inserts to 0 if not + supported, 1 if it is supported. + + ARGS + + check_if_ignore_table() + table_name Table name to check + table_type Type of table + + GLOBAL VARIABLES + mysql MySQL connection + verbose Write warning messages + + RETURN + char (bit value) See IGNORE_ values at top +*/ + +char check_if_ignore_table(const char *table_name, char *table_type) +{ + char result= IGNORE_NONE; + char buff[FN_REFLEN+80], show_name_buff[FN_REFLEN]; + MYSQL_RES *res= NULL; + MYSQL_ROW row; + DBUG_ENTER("check_if_ignore_table"); + + /* Check memory for quote_for_like() */ + DBUG_ASSERT(2*sizeof(table_name) < sizeof(show_name_buff)); + my_snprintf(buff, sizeof(buff), + "SELECT engine, table_type FROM INFORMATION_SCHEMA.TABLES " + "WHERE table_schema = DATABASE() AND table_name = %s", + quote_for_equal(table_name, show_name_buff)); + if (mysql_query_with_error_report(mysql, &res, buff)) + { + if (mysql_errno(mysql) != ER_PARSE_ERROR) + { /* If old MySQL version */ + verbose_msg("-- Warning: Couldn't get status information for " + "table %s (%s)\n", table_name, mysql_error(mysql)); + DBUG_RETURN(result); /* assume table is ok */ + } + } + if (!(row= mysql_fetch_row(res))) + { + fprintf(stderr, + "Error: Couldn't read status information for table %s (%s)\n", + table_name, mysql_error(mysql)); + mysql_free_result(res); + DBUG_RETURN(result); /* assume table is ok */ + } + if (!(row[0])) + strmake(table_type, "VIEW", NAME_LEN-1); + else + { + /* + If the table type matches any of these, we do support delayed inserts. + Note: we do not want to skip dumping this table if if is not one of + these types, but we do want to use delayed inserts in the dump if + the table type is _NOT_ one of these types + */ + strmake(table_type, row[0], NAME_LEN-1); + if (opt_delayed) + { + if (strcmp(table_type,"MyISAM") && + strcmp(table_type,"ISAM") && + strcmp(table_type,"ARCHIVE") && + strcmp(table_type,"HEAP") && + strcmp(table_type,"MEMORY")) + result= IGNORE_INSERT_DELAYED; + } + if (!strcmp(row[1],"SEQUENCE")) + result|= IGNORE_SEQUENCE_TABLE; + + if (!strcmp(table_type, "S3")) + result|= IGNORE_S3_TABLE; + + /* + If these two types, we do want to skip dumping the table + */ + if (!opt_no_data && opt_no_data_med) + { + const char *found= strstr(" " MED_ENGINES ",", table_type); + if (found && found[-1] == ' ' && found[strlen(table_type)] == ',') + result= IGNORE_DATA; + } + } + mysql_free_result(res); + DBUG_RETURN(result); +} + + +/* + Get string of comma-separated primary key field names + + SYNOPSIS + char *primary_key_fields(const char *table_name) + RETURNS pointer to allocated buffer (must be freed by caller) + table_name quoted table name + + DESCRIPTION + Use SHOW KEYS FROM table_name, allocate a buffer to hold the + field names, and then build that string and return the pointer + to that buffer. + + Returns NULL if there is no PRIMARY or UNIQUE key on the table, + or if there is some failure. It is better to continue to dump + the table unsorted, rather than exit without dumping the data. +*/ + +static char *primary_key_fields(const char *table_name) +{ + MYSQL_RES *res= NULL; + MYSQL_ROW row; + /* SHOW KEYS FROM + table name * 2 (escaped) + 2 quotes + \0 */ + char show_keys_buff[15 + NAME_LEN * 2 + 3]; + size_t result_length= 0; + char *result= 0; + char buff[NAME_LEN * 2 + 3]; + char *quoted_field; + + my_snprintf(show_keys_buff, sizeof(show_keys_buff), + "SHOW KEYS FROM %s", table_name); + if (mysql_query(mysql, show_keys_buff) || + !(res= mysql_store_result(mysql))) + { + fprintf(stderr, "Warning: Couldn't read keys from table %s;" + " records are NOT sorted (%s)\n", + table_name, mysql_error(mysql)); + /* Don't exit, because it's better to print out unsorted records */ + goto cleanup; + } + + /* + * Figure out the length of the ORDER BY clause result. + * Note that SHOW KEYS is ordered: a PRIMARY key is always the first + * row, and UNIQUE keys come before others. So we only need to check + * the first key, not all keys. + */ + if ((row= mysql_fetch_row(res)) && atoi(row[1]) == 0) + { + /* Key is unique */ + do + { + quoted_field= quote_name(row[4], buff, 0); + result_length+= strlen(quoted_field) + 1; /* + 1 for ',' or \0 */ + } while ((row= mysql_fetch_row(res)) && atoi(row[3]) > 1); + } + + /* Build the ORDER BY clause result */ + if (result_length) + { + char *end; + /* result (terminating \0 is already in result_length) */ + result= my_malloc(PSI_NOT_INSTRUMENTED, result_length + 10, MYF(MY_WME)); + if (!result) + { + fprintf(stderr, "Error: Not enough memory to store ORDER BY clause\n"); + goto cleanup; + } + mysql_data_seek(res, 0); + row= mysql_fetch_row(res); + quoted_field= quote_name(row[4], buff, 0); + end= strmov(result, quoted_field); + while ((row= mysql_fetch_row(res)) && atoi(row[3]) > 1) + { + quoted_field= quote_name(row[4], buff, 0); + end= strxmov(end, ",", quoted_field, NullS); + } + } + +cleanup: + if (res) + mysql_free_result(res); + + return result; +} + + +/* + Replace a substring + + SYNOPSIS + replace + ds_str The string to search and perform the replace in + search_str The string to search for + search_len Length of the string to search for + replace_str The string to replace with + replace_len Length of the string to replace with + + RETURN + 0 String replaced + 1 Could not find search_str in str +*/ + +static int replace(DYNAMIC_STRING *ds_str, + const char *search_str, ulong search_len, + const char *replace_str, ulong replace_len) +{ + DYNAMIC_STRING ds_tmp; + const char *start= strstr(ds_str->str, search_str); + if (!start) + return 1; + init_dynamic_string_checked(&ds_tmp, "", + ds_str->length + replace_len, 256); + dynstr_append_mem_checked(&ds_tmp, ds_str->str, (uint)(start - ds_str->str)); + dynstr_append_mem_checked(&ds_tmp, replace_str, replace_len); + dynstr_append_checked(&ds_tmp, start + search_len); + dynstr_set_checked(ds_str, ds_tmp.str); + dynstr_free(&ds_tmp); + return 0; +} + + +/* + Getting VIEW structure + + SYNOPSIS + get_view_structure() + table view name + db db name + + RETURN + 0 OK + 1 ERROR +*/ + +static my_bool get_view_structure(char *table, char* db) +{ + MYSQL_RES *table_res; + MYSQL_ROW row; + MYSQL_FIELD *field; + char *result_table, *opt_quoted_table; + char table_buff[NAME_LEN*2+3]; + char table_buff2[NAME_LEN*2+3]; + char query[QUERY_LENGTH]; + FILE *sql_file= md_result_file; + DBUG_ENTER("get_view_structure"); + + if (opt_no_create_info) /* Don't write table creation info */ + DBUG_RETURN(0); + + verbose_msg("-- Retrieving view structure for table %s...\n", table); + +#ifdef NOT_REALLY_USED_YET + dynstr_append_checked(&insert_pat, "SET SQL_QUOTE_SHOW_CREATE="); + dynstr_append_checked(&insert_pat, (opt_quoted || opt_keywords)? "1":"0"); +#endif + + result_table= quote_name(table, table_buff, 1); + opt_quoted_table= quote_name(table, table_buff2, 0); + + if (switch_character_set_results(mysql, "binary")) + DBUG_RETURN(1); + + my_snprintf(query, sizeof(query), "SHOW CREATE TABLE %s", result_table); + + if (mysql_query_with_error_report(mysql, &table_res, query)) + { + switch_character_set_results(mysql, default_charset); + DBUG_RETURN(0); + } + + /* Check if this is a view */ + field= mysql_fetch_field_direct(table_res, 0); + if (strcmp(field->name, "View") != 0) + { + mysql_free_result(table_res); + switch_character_set_results(mysql, default_charset); + verbose_msg("-- It's base table, skipped\n"); + DBUG_RETURN(0); + } + + /* If requested, open separate .sql file for this view */ + if (path) + { + if (!(sql_file= open_sql_file_for_table(table, O_WRONLY))) + { + mysql_free_result(table_res); + DBUG_RETURN(1); + } + write_header(sql_file, db); + } + + print_comment(sql_file, 0, + "\n--\n-- Final view structure for view %s\n--\n\n", + fix_for_comment(result_table)); + + /* View might not exist if this view was dumped with --tab. */ + fprintf(sql_file, "/*!50001 DROP VIEW IF EXISTS %s*/;\n", opt_quoted_table); + + my_snprintf(query, sizeof(query), + "SELECT CHECK_OPTION, DEFINER, SECURITY_TYPE, " + " CHARACTER_SET_CLIENT, COLLATION_CONNECTION " + "FROM information_schema.views " + "WHERE table_name=\"%s\" AND table_schema=\"%s\"", table, db); + + if (mysql_query(mysql, query)) + { + /* + Use the raw output from SHOW CREATE TABLE if + information_schema query fails. + */ + row= mysql_fetch_row(table_res); + fprintf(sql_file, "/*!50001 %s */;\n", row[1]); + check_io(sql_file); + mysql_free_result(table_res); + } + else + { + char *ptr; + ulong *lengths; + char search_buf[256], replace_buf[256]; + ulong search_len, replace_len; + DYNAMIC_STRING ds_view; + + /* Save the result of SHOW CREATE TABLE in ds_view */ + row= mysql_fetch_row(table_res); + lengths= mysql_fetch_lengths(table_res); + init_dynamic_string_checked(&ds_view, row[1], lengths[1] + 1, 1024); + mysql_free_result(table_res); + + /* Get the result from "select ... information_schema" */ + if (!(table_res= mysql_store_result(mysql)) || + !(row= mysql_fetch_row(table_res))) + { + if (table_res) + mysql_free_result(table_res); + dynstr_free(&ds_view); + DB_error(mysql, "when trying to save the result of SHOW CREATE TABLE in ds_view."); + DBUG_RETURN(1); + } + + lengths= mysql_fetch_lengths(table_res); + + /* + "WITH %s CHECK OPTION" is available from 5.0.2 + Surround it with !50002 comments + */ + if (strcmp(row[0], "NONE")) + { + + ptr= search_buf; + search_len= (ulong)(strxmov(ptr, "WITH ", row[0], + " CHECK OPTION", NullS) - ptr); + ptr= replace_buf; + replace_len=(ulong)(strxmov(ptr, "*/\n/*!50002 WITH ", row[0], + " CHECK OPTION", NullS) - ptr); + replace(&ds_view, search_buf, search_len, replace_buf, replace_len); + } + + /* + "DEFINER=%s SQL SECURITY %s" is available from 5.0.13 + Surround it with !50013 comments + */ + { + size_t user_name_len; + char user_name_str[USERNAME_LENGTH + 1]; + char quoted_user_name_str[USERNAME_LENGTH * 2 + 3]; + size_t host_name_len; + char host_name_str[HOSTNAME_LENGTH + 1]; + char quoted_host_name_str[HOSTNAME_LENGTH * 2 + 3]; + + parse_user(row[1], lengths[1], user_name_str, &user_name_len, + host_name_str, &host_name_len); + + ptr= search_buf; + search_len= + (ulong)(strxmov(ptr, "DEFINER=", + quote_name(user_name_str, quoted_user_name_str, FALSE), + "@", + quote_name(host_name_str, quoted_host_name_str, FALSE), + " SQL SECURITY ", row[2], NullS) - ptr); + ptr= replace_buf; + replace_len= + (ulong)(strxmov(ptr, "*/\n/*!50013 DEFINER=", + quote_name(user_name_str, quoted_user_name_str, FALSE), + "@", + quote_name(host_name_str, quoted_host_name_str, FALSE), + " SQL SECURITY ", row[2], + " */\n/*!50001", NullS) - ptr); + replace(&ds_view, search_buf, search_len, replace_buf, replace_len); + } + + /* Dump view structure to file */ + + fprintf(sql_file, + "/*!50001 SET @saved_cs_client = @@character_set_client */;\n" + "/*!50001 SET @saved_cs_results = @@character_set_results */;\n" + "/*!50001 SET @saved_col_connection = @@collation_connection */;\n" + "/*!50001 SET character_set_client = %s */;\n" + "/*!50001 SET character_set_results = %s */;\n" + "/*!50001 SET collation_connection = %s */;\n" + "/*!50001 %s */;\n" + "/*!50001 SET character_set_client = @saved_cs_client */;\n" + "/*!50001 SET character_set_results = @saved_cs_results */;\n" + "/*!50001 SET collation_connection = @saved_col_connection */;\n", + (const char *) row[3], + (const char *) row[3], + (const char *) row[4], + (const char *) ds_view.str); + + check_io(sql_file); + mysql_free_result(table_res); + dynstr_free(&ds_view); + } + + switch_character_set_results(mysql, default_charset); + + /* If a separate .sql file was opened, close it now */ + if (sql_file != md_result_file) + { + fputs("\n", sql_file); + write_footer(sql_file); + my_fclose(sql_file, MYF(MY_WME)); + } + DBUG_RETURN(0); +} + +/* + The following functions are wrappers for the dynamic string functions + and if they fail, the wrappers will terminate the current process. +*/ + +#define DYNAMIC_STR_ERROR_MSG "Couldn't perform DYNAMIC_STRING operation" + +static void init_dynamic_string_checked(DYNAMIC_STRING *str, const char *init_str, + size_t init_alloc, size_t alloc_increment) +{ + if (init_dynamic_string(str, init_str, init_alloc, alloc_increment)) + die(EX_MYSQLERR, DYNAMIC_STR_ERROR_MSG); +} + +static void dynstr_append_checked(DYNAMIC_STRING* dest, const char* src) +{ + if (dynstr_append(dest, src)) + die(EX_MYSQLERR, DYNAMIC_STR_ERROR_MSG); +} + +static void dynstr_set_checked(DYNAMIC_STRING *str, const char *init_str) +{ + if (dynstr_set(str, init_str)) + die(EX_MYSQLERR, DYNAMIC_STR_ERROR_MSG); +} + +static void dynstr_append_mem_checked(DYNAMIC_STRING *str, const char *append, + uint length) +{ + if (dynstr_append_mem(str, append, length)) + die(EX_MYSQLERR, DYNAMIC_STR_ERROR_MSG); +} + +static void dynstr_realloc_checked(DYNAMIC_STRING *str, ulong additional_size) +{ + if (dynstr_realloc(str, additional_size)) + die(EX_MYSQLERR, DYNAMIC_STR_ERROR_MSG); +} + + +int main(int argc, char **argv) +{ + char query[48]; + char bin_log_name[FN_REFLEN]; + int exit_code; + int consistent_binlog_pos= 0; + int have_mariadb_gtid= 0; + MY_INIT(argv[0]); + + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + compatible_mode_normal_str[0]= 0; + default_charset= (char *)mysql_universal_client_charset; + + exit_code= get_options(&argc, &argv); + if (exit_code) + { + free_resources(); + exit(exit_code); + } + sf_leaking_memory=0; /* from now on we cleanup properly */ + + /* + Disable comments in xml mode if 'comments' option is not explicitly used. + */ + if (opt_xml && !opt_comments_used) + opt_comments= 0; + + if (log_error_file) + { + if(!(stderror_file= freopen(log_error_file, "a+", stderr))) + { + free_resources(); + exit(EX_MYSQLERR); + } + } + + if (connect_to_db(current_host, current_user, opt_password)) + { + free_resources(); + exit(EX_MYSQLERR); + } + if (!path) + write_header(md_result_file, *argv); + + /* Set MAX_STATEMENT_TIME to 0 unless set in client */ + my_snprintf(query, sizeof(query), "/*!100100 SET @@MAX_STATEMENT_TIME=%f */", opt_max_statement_time); + mysql_query(mysql, query); + + /* Set server side timeout between client commands to server compiled-in default */ + mysql_query(mysql, "/*!100100 SET WAIT_TIMEOUT=DEFAULT */"); + + /* Check if the server support multi source */ + if (mysql_get_server_version(mysql) >= 100000) + { + multi_source= 2; + have_mariadb_gtid= 1; + } + + if (opt_slave_data && do_stop_slave_sql(mysql)) + goto err; + + if (opt_single_transaction && opt_master_data) + { + /* See if we can avoid FLUSH TABLES WITH READ LOCK (MariaDB 5.3+). */ + consistent_binlog_pos= check_consistent_binlog_pos(NULL, NULL); + } + + if ((opt_lock_all_tables || (opt_master_data && !consistent_binlog_pos) || + (opt_single_transaction && flush_logs)) && + do_flush_tables_read_lock(mysql)) + goto err; + + /* + Flush logs before starting transaction since + this causes implicit commit starting mysql-5.5. + */ + if (opt_lock_all_tables || opt_master_data || + (opt_single_transaction && flush_logs) || + opt_delete_master_logs) + { + if (flush_logs || opt_delete_master_logs) + { + if (mysql_refresh(mysql, REFRESH_LOG)) + { + fprintf(stderr, + "Flush logs or delete master logs failure in server \n"); + first_error= EX_MYSQLERR; + goto err; + } + verbose_msg("-- main : logs flushed successfully!\n"); + } + + /* Not anymore! That would not be sensible. */ + flush_logs= 0; + } + + if (opt_delete_master_logs) + { + if (get_bin_log_name(mysql, bin_log_name, sizeof(bin_log_name))) + goto err; + } + + if (opt_single_transaction && start_transaction(mysql)) + goto err; + + /* Add 'STOP SLAVE to beginning of dump */ + if (opt_slave_apply && add_stop_slave()) + goto err; + + if (opt_master_data && do_show_master_status(mysql, consistent_binlog_pos, + have_mariadb_gtid, opt_use_gtid)) + goto err; + if (opt_slave_data && do_show_slave_status(mysql, opt_use_gtid, + have_mariadb_gtid)) + goto err; + if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */ + goto err; + + if (opt_alltspcs) + dump_all_tablespaces(); + + if (extended_insert) + init_dynamic_string_checked(&extended_row, "", 1024, 1024); + + if (opt_alldbs) + { + if (!opt_alltspcs && !opt_notspcs) + dump_all_tablespaces(); + dump_all_databases(); + } + else + { + // Check all arguments meet length condition. Currently database and table + // names are limited to NAME_LEN bytes and stack-based buffers assumes + // that escaped name will be not longer than NAME_LEN*2 + 2 bytes long. + int argument; + for (argument= 0; argument < argc; argument++) + { + size_t argument_length= strlen(argv[argument]); + if (argument_length > NAME_LEN) + { + die(EX_CONSCHECK, "[ERROR] Argument '%s' is too long, it cannot be " + "name for any table or database.\n", argv[argument]); + } + } + + if (argc > 1 && !opt_databases) + { + /* Only one database and selected table(s) */ + if (!opt_alltspcs && !opt_notspcs) + dump_tablespaces_for_tables(*argv, (argv + 1), (argc - 1)); + dump_selected_tables(*argv, (argv + 1), (argc - 1)); + } + else if (argc > 0) + { + /* One or more databases, all tables */ + if (!opt_alltspcs && !opt_notspcs) + dump_tablespaces_for_databases(argv); + dump_databases(argv); + } + } + + if (opt_system & OPT_SYSTEM_PLUGINS) + dump_all_plugins(); + + if (opt_system & OPT_SYSTEM_USERS) + dump_all_users_roles_and_grants(); + + if (opt_system & OPT_SYSTEM_UDFS) + dump_all_udfs(); + + if (opt_system & OPT_SYSTEM_SERVERS) + dump_all_servers(); + + /* These must be last as they explicitly change the current database to mysql */ + if (opt_system & OPT_SYSTEM_STATS) + dump_all_stats(); + + if (opt_system & OPT_SYSTEM_TIMEZONES) + dump_all_timezones(); + + /* add 'START SLAVE' to end of dump */ + if (opt_slave_apply && add_slave_statements()) + goto err; + + /* ensure dumped data flushed */ + if (md_result_file && fflush(md_result_file)) + { + if (!first_error) + first_error= EX_MYSQLERR; + goto err; + } + /* everything successful, purge the old logs files */ + if (opt_delete_master_logs && purge_bin_logs_to(mysql, bin_log_name)) + goto err; + + /* + No reason to explicitly COMMIT the transaction, neither to explicitly + UNLOCK TABLES: these will be automatically be done by the server when we + disconnect now. Saves some code here, some network trips, adds nothing to + server. + */ +err: + /* if --dump-slave , start the slave sql thread */ + if (opt_slave_data) + do_start_slave_sql(mysql); + + dbDisconnect(current_host); + if (!path) + write_footer(md_result_file); + free_resources(); + + if (stderror_file) + fclose(stderror_file); + + return(first_error); +} /* main */ diff --git a/client/mysqlimport.c b/client/mysqlimport.c new file mode 100644 index 00000000..a7c60e2b --- /dev/null +++ b/client/mysqlimport.c @@ -0,0 +1,788 @@ +/* + Copyright (c) 2000, 2015, Oracle and/or its affiliates. + Copyright (c) 2011, 2022, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* +** mysqlimport.c - Imports all given files +** into a table(s). +** +** ************************* +** * * +** * AUTHOR: Monty & Jani * +** * DATE: June 24, 1997 * +** * * +** ************************* +*/ +#define IMPORT_VERSION "3.7" + +#include "client_priv.h" +#include + +#include "mysql_version.h" + +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + + +/* Global Thread counter */ +uint counter= 0; +pthread_mutex_t init_mutex; +pthread_mutex_t counter_mutex; +pthread_cond_t count_threshhold; + +static void db_error_with_table(MYSQL *mysql, char *table); +static void db_error(MYSQL *mysql); +static char *field_escape(char *to,const char *from,uint length); +static char *add_load_option(char *ptr,const char *object, + const char *statement); + +static my_bool verbose=0,lock_tables=0,ignore_errors=0,opt_delete=0, + replace, silent, ignore, ignore_foreign_keys, + opt_compress, opt_low_priority, tty_password; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static uint opt_use_threads=0, opt_local_file=0, my_end_arg= 0; +static char *opt_password=0, *current_user=0, + *current_host=0, *current_db=0, *fields_terminated=0, + *lines_terminated=0, *enclosed=0, *opt_enclosed=0, + *escaped=0, *opt_columns=0, + *default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME; +static uint opt_mysql_port= 0, opt_protocol= 0; +static char * opt_mysql_unix_port=0; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; +static longlong opt_ignore_lines= -1; + +#include + +static char **argv_to_free; + +static struct my_option my_long_options[] = +{ + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", (char**) &charsets_dir, + (char**) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"columns", 'c', + "Use only these columns to import the data to. Give the column names in a comma separated list. This is same as giving columns to LOAD DATA INFILE.", + &opt_columns, &opt_columns, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"debug",'#', "Output debug log. Often this is 'd:t:o,filename'.", 0, 0, 0, + GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"delete", 'd', "First delete all rows from table.", &opt_delete, + &opt_delete, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"fields-terminated-by", OPT_FTB, + "Fields in the input file are terminated by the given string.", + &fields_terminated, &fields_terminated, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"fields-enclosed-by", OPT_ENC, + "Fields in the import file are enclosed by the given character.", + &enclosed, &enclosed, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"fields-optionally-enclosed-by", OPT_O_ENC, + "Fields in the input file are optionally enclosed by the given character.", + &opt_enclosed, &opt_enclosed, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"fields-escaped-by", OPT_ESC, + "Fields in the input file are escaped by the given character.", + &escaped, &escaped, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, + 0, 0}, + {"force", 'f', "Continue even if we get an SQL error.", + &ignore_errors, &ignore_errors, 0, GET_BOOL, NO_ARG, 0, 0, + 0, 0, 0, 0}, + {"help", '?', "Displays this help and exits.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", ¤t_host, + ¤t_host, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ignore", 'i', "If duplicate unique key was found, keep old row.", + &ignore, &ignore, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"ignore-foreign-keys", 'k', + "Disable foreign key checks while importing the data.", + &ignore_foreign_keys, &ignore_foreign_keys, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"ignore-lines", OPT_IGN_LINES, "Ignore first n lines of data infile.", + &opt_ignore_lines, &opt_ignore_lines, 0, GET_LL, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"lines-terminated-by", OPT_LTB, + "Lines in the input file are terminated by the given string.", + &lines_terminated, &lines_terminated, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"local", 'L', "Read all files through the client.", &opt_local_file, + &opt_local_file, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"lock-tables", 'l', "Lock all tables for write (this disables threads).", + &lock_tables, &lock_tables, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"low-priority", OPT_LOW_PRIORITY, + "Use LOW_PRIORITY when updating the table.", &opt_low_priority, + &opt_low_priority, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given it's asked from the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"replace", 'r', "If duplicate unique key was found, replace old row.", + &replace, &replace, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Be more silent.", &silent, &silent, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include + {"use-threads", OPT_USE_THREADS, + "Load files in parallel. The argument is the number " + "of threads to use for loading data.", + &opt_use_threads, &opt_use_threads, 0, + GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", ¤t_user, + ¤t_user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', "Print info about the various stages.", &verbose, + &verbose, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static const char *load_default_groups[]= +{ "mysqlimport", "mariadb-import", "client", "client-server", "client-mariadb", + 0 }; + + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n" ,my_progname, + IMPORT_VERSION, MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + + +static void usage(void) +{ + puts("Copyright 2000-2008 MySQL AB, 2008 Sun Microsystems, Inc."); + puts("Copyright 2008-2011 Oracle and Monty Program Ab."); + puts("Copyright 2012-2019 MariaDB Corporation Ab."); + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("\ +Loads tables from text files in various formats. The base name of the\n\ +text file must be the name of the table that should be used.\n\ +If one uses sockets to connect to the MariaDB server, the server will open\n\ +and read the text file directly. In other cases the client will open the text\n\ +file. The SQL command 'LOAD DATA INFILE' is used to import the rows.\n"); + + printf("\nUsage: %s [OPTIONS] database textfile...\n",my_progname); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +static my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + switch(opt->id) { + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password=my_strdup(PSI_NOT_INSTRUMENTED, argument,MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]=0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password= 1; + break; +#ifdef _WIN32 + case 'W': + opt_protocol = MYSQL_PROTOCOL_PIPE; + opt_local_file=1; + break; +#endif + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + case '#': + DBUG_PUSH(argument ? argument : "d:t:o"); + debug_check_flag= 1; + break; +#include + case 'V': print_version(); exit(0); + case 'I': + case '?': + usage(); + exit(0); + } + return 0; +} + + +static int get_options(int *argc, char ***argv) +{ + int ho_error; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + if (enclosed && opt_enclosed) + { + fprintf(stderr, "You can't use ..enclosed.. and ..optionally-enclosed.. at the same time.\n"); + return(1); + } + if (replace && ignore) + { + fprintf(stderr, "You can't use --ignore (-i) and --replace (-r) at the same time.\n"); + return(1); + } + if (*argc < 2) + { + usage(); + return 1; + } + current_db= *((*argv)++); + (*argc)--; + if (tty_password) + opt_password=my_get_tty_password(NullS); + return(0); +} + + + +static int write_to_table(char *filename, MYSQL *mysql) +{ + char tablename[FN_REFLEN], hard_path[FN_REFLEN], + escaped_name[FN_REFLEN * 2 + 1], + sql_statement[FN_REFLEN*16+256], *end, *pos; + DBUG_ENTER("write_to_table"); + DBUG_PRINT("enter",("filename: %s",filename)); + + fn_format(tablename, filename, "", "", 1 | 2); /* removes path & ext. */ + if (!opt_local_file) + strmov(hard_path,filename); + else + my_load_path(hard_path, filename, NULL); /* filename includes the path */ + + if (opt_delete) + { + if (verbose) + fprintf(stdout, "Deleting the old data from table %s\n", tablename); + snprintf(sql_statement, FN_REFLEN*16+256, "DELETE FROM %s", tablename); + if (mysql_query(mysql, sql_statement)) + { + db_error_with_table(mysql, tablename); + DBUG_RETURN(1); + } + } + to_unix_path(hard_path); + if (verbose) + { + if (opt_local_file) + fprintf(stdout, "Loading data from LOCAL file: %s into %s\n", + hard_path, tablename); + else + fprintf(stdout, "Loading data from SERVER file: %s into %s\n", + hard_path, tablename); + } + mysql_real_escape_string(mysql, escaped_name, hard_path, + (unsigned long) strlen(hard_path)); + sprintf(sql_statement, "LOAD DATA %s %s INFILE '%s'", + opt_low_priority ? "LOW_PRIORITY" : "", + opt_local_file ? "LOCAL" : "", escaped_name); + end= strend(sql_statement); + if (replace) + end= strmov(end, " REPLACE"); + if (ignore) + end= strmov(end, " IGNORE"); + end= strmov(end, " INTO TABLE `"); + /* Turn any ` into `` in table name. */ + for (pos= tablename; *pos; pos++) + { + if (*pos == '`') + *end++= '`'; + *end++= *pos; + } + end= strmov(end, "`"); + + if (fields_terminated || enclosed || opt_enclosed || escaped) + end= strmov(end, " FIELDS"); + end= add_load_option(end, fields_terminated, " TERMINATED BY"); + end= add_load_option(end, enclosed, " ENCLOSED BY"); + end= add_load_option(end, opt_enclosed, + " OPTIONALLY ENCLOSED BY"); + end= add_load_option(end, escaped, " ESCAPED BY"); + end= add_load_option(end, lines_terminated, " LINES TERMINATED BY"); + if (opt_ignore_lines >= 0) + end= strmov(longlong10_to_str(opt_ignore_lines, + strmov(end, " IGNORE "),10), " LINES"); + if (opt_columns) + end= strmov(strmov(strmov(end, " ("), opt_columns), ")"); + *end= '\0'; + + if (mysql_query(mysql, sql_statement)) + { + db_error_with_table(mysql, tablename); + DBUG_RETURN(1); + } + if (!silent) + { + if (mysql_info(mysql)) /* If NULL-pointer, print nothing */ + { + fprintf(stdout, "%s.%s: %s\n", current_db, tablename, + mysql_info(mysql)); + } + } + DBUG_RETURN(0); +} + + + +static void lock_table(MYSQL *mysql, int tablecount, char **raw_tablename) +{ + DYNAMIC_STRING query; + int i; + char tablename[FN_REFLEN]; + + if (verbose) + fprintf(stdout, "Locking tables for write\n"); + init_dynamic_string(&query, "LOCK TABLES ", 256, 1024); + for (i=0 ; i < tablecount ; i++) + { + fn_format(tablename, raw_tablename[i], "", "", 1 | 2); + dynstr_append(&query, tablename); + dynstr_append(&query, " WRITE,"); + } + if (mysql_real_query(mysql, query.str, (ulong)query.length-1)) + db_error(mysql); /* We shall countinue here, if --force was given */ +} + + + + +static MYSQL *db_connect(char *host, char *database, + char *user, char *passwd) +{ + MYSQL *mysql; + my_bool reconnect; + if (verbose) + fprintf(stdout, "Connecting to %s\n", host ? host : "localhost"); + if (opt_use_threads && !lock_tables) + { + pthread_mutex_lock(&init_mutex); + if (!(mysql= mysql_init(NULL))) + { + pthread_mutex_unlock(&init_mutex); + return 0; + } + pthread_mutex_unlock(&init_mutex); + } + else + if (!(mysql= mysql_init(NULL))) + return 0; + if (opt_compress) + mysql_options(mysql,MYSQL_OPT_COMPRESS,NullS); + if (opt_local_file) + mysql_options(mysql,MYSQL_OPT_LOCAL_INFILE, + (char*) &opt_local_file); +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); + mysql_options(mysql, MYSQL_SET_CHARSET_NAME, my_default_csname()); + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqlimport"); + if (!(mysql_real_connect(mysql,host,user,passwd, + database,opt_mysql_port,opt_mysql_unix_port, + 0))) + { + ignore_errors=0; /* NO RETURN FROM db_error */ + db_error(mysql); + } + reconnect= 0; + mysql_options(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (verbose) + fprintf(stdout, "Selecting database %s\n", database); + if (mysql_select_db(mysql, database)) + { + ignore_errors=0; + db_error(mysql); + } + if (ignore_foreign_keys) + mysql_query(mysql, "set foreign_key_checks= 0;"); + + return mysql; +} + + + +static void db_disconnect(char *host, MYSQL *mysql) +{ + if (verbose) + fprintf(stdout, "Disconnecting from %s\n", host ? host : "localhost"); + mysql_close(mysql); +} + + +static void safe_exit(int error, MYSQL *mysql) +{ + if (error && ignore_errors) + return; + + /* in multi-threaded mode protect from concurrent safe_exit's */ + if (counter) + pthread_mutex_lock(&counter_mutex); + + if (mysql) + mysql_close(mysql); + + if (counter) + { + /* dirty exit. some threads are running, + memory is not freed, openssl not deinitialized */ + DBUG_ASSERT(error); + _exit(error); + } + + mysql_library_end(); + free_defaults(argv_to_free); + my_free(opt_password); + my_end(my_end_arg); /* clean exit */ + exit(error); +} + + + +static void db_error_with_table(MYSQL *mysql, char *table) +{ + my_printf_error(0,"Error: %d, %s, when using table: %s", + MYF(0), mysql_errno(mysql), mysql_error(mysql), table); + safe_exit(1, mysql); +} + + + +static void db_error(MYSQL *mysql) +{ + my_printf_error(0,"Error: %d %s", MYF(0), mysql_errno(mysql), mysql_error(mysql)); + safe_exit(1, mysql); +} + + +static char *add_load_option(char *ptr, const char *object, + const char *statement) +{ + if (object) + { + /* Don't escape hex constants */ + if (object[0] == '0' && (object[1] == 'x' || object[1] == 'X')) + ptr= strxmov(ptr," ",statement," ",object,NullS); + else + { + /* char constant; escape */ + ptr= strxmov(ptr," ",statement," '",NullS); + ptr= field_escape(ptr,object,(uint) strlen(object)); + *ptr++= '\''; + } + } + return ptr; +} + +/* +** Allow the user to specify field terminator strings like: +** "'", "\", "\\" (escaped backslash), "\t" (tab), "\n" (newline) +** This is done by doubling ' and add a end -\ if needed to avoid +** syntax errors from the SQL parser. +*/ + +static char *field_escape(char *to,const char *from,uint length) +{ + const char *end; + uint end_backslashes=0; + + for (end= from+length; from != end; from++) + { + *to++= *from; + if (*from == '\\') + end_backslashes^=1; /* find odd number of backslashes */ + else + { + if (*from == '\'' && !end_backslashes) + *to++= *from; /* We want a duplicate of "'" for MySQL */ + end_backslashes=0; + } + } + /* Add missing backslashes if user has specified odd number of backs.*/ + if (end_backslashes) + *to++= '\\'; + return to; +} + +int exitcode= 0; + +pthread_handler_t worker_thread(void *arg) +{ + int error; + char *raw_table_name= (char *)arg; + MYSQL *mysql= 0; + + if (mysql_thread_init()) + goto error; + + if (!(mysql= db_connect(current_host,current_db,current_user,opt_password))) + { + goto error; + } + + if (mysql_query(mysql, "/*!40101 set @@character_set_database=binary */;")) + { + db_error(mysql); /* We shall countinue here, if --force was given */ + goto error; + } + + /* + We are not currently catching the error here. + */ + if((error= write_to_table(raw_table_name, mysql))) + if (exitcode == 0) + exitcode= error; + +error: + if (mysql) + db_disconnect(current_host, mysql); + + pthread_mutex_lock(&counter_mutex); + counter--; + pthread_cond_signal(&count_threshhold); + pthread_mutex_unlock(&counter_mutex); + mysql_thread_end(); + pthread_exit(0); + return 0; +} + + +int main(int argc, char **argv) +{ + int error=0; + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + /* argv is changed in the program */ + argv_to_free= argv; + if (get_options(&argc, &argv)) + { + free_defaults(argv_to_free); + return(1); + } + + sf_leaking_memory=0; /* from now on we cleanup properly */ + + if (opt_use_threads && !lock_tables) + { + char **save_argv; + uint worker_thread_count= 0, table_count= 0, i= 0; + pthread_t *worker_threads; /* Thread descriptor */ + pthread_attr_t attr; /* Thread attributes */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, + PTHREAD_CREATE_JOINABLE); + + pthread_mutex_init(&init_mutex, NULL); + pthread_mutex_init(&counter_mutex, NULL); + pthread_cond_init(&count_threshhold, NULL); + + /* Count the number of tables. This number denotes the total number + of threads spawn. + */ + save_argv= argv; + for (table_count= 0; *argv != NULL; argv++) + table_count++; + argv= save_argv; + + if (!(worker_threads= (pthread_t*) my_malloc(PSI_NOT_INSTRUMENTED, + table_count * sizeof(*worker_threads), MYF(0)))) + return -2; + + for (; *argv != NULL; argv++) /* Loop through tables */ + { + pthread_mutex_lock(&counter_mutex); + while (counter == opt_use_threads) + { + struct timespec abstime; + + set_timespec(abstime, 3); + pthread_cond_timedwait(&count_threshhold, &counter_mutex, &abstime); + } + /* Before exiting the lock we set ourselves up for the next thread */ + counter++; + pthread_mutex_unlock(&counter_mutex); + /* now create the thread */ + if (pthread_create(&worker_threads[worker_thread_count], &attr, + worker_thread, (void *)*argv) != 0) + { + pthread_mutex_lock(&counter_mutex); + counter--; + pthread_mutex_unlock(&counter_mutex); + fprintf(stderr,"%s: Could not create thread\n", my_progname); + continue; + } + worker_thread_count++; + } + + /* + We loop until we know that all children have cleaned up. + */ + pthread_mutex_lock(&counter_mutex); + while (counter) + { + struct timespec abstime; + + set_timespec(abstime, 3); + pthread_cond_timedwait(&count_threshhold, &counter_mutex, &abstime); + } + pthread_mutex_unlock(&counter_mutex); + pthread_mutex_destroy(&init_mutex); + pthread_mutex_destroy(&counter_mutex); + pthread_cond_destroy(&count_threshhold); + pthread_attr_destroy(&attr); + + for(i= 0; i < worker_thread_count; i++) + { + if (pthread_join(worker_threads[i], NULL)) + fprintf(stderr,"%s: Could not join worker thread.\n", my_progname); + } + + my_free(worker_threads); + } + else + { + MYSQL *mysql= 0; + if (!(mysql= db_connect(current_host,current_db,current_user,opt_password))) + { + free_defaults(argv_to_free); + return(1); /* purecov: deadcode */ + } + + if (mysql_query(mysql, "/*!40101 set @@character_set_database=binary */;")) + { + db_error(mysql); /* We shall countinue here, if --force was given */ + return(1); + } + + if (lock_tables) + lock_table(mysql, argc, argv); + for (; *argv != NULL; argv++) + if ((error= write_to_table(*argv, mysql))) + if (exitcode == 0) + exitcode= error; + db_disconnect(current_host, mysql); + } + safe_exit(0, 0); + return(exitcode); +} diff --git a/client/mysqlshow.c b/client/mysqlshow.c new file mode 100644 index 00000000..aa606d61 --- /dev/null +++ b/client/mysqlshow.c @@ -0,0 +1,964 @@ +/* + Copyright (c) 2000, 2015, Oracle and/or its affiliates. + Copyright (c) 2010, 2019, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* Show databases, tables or columns */ + +#define SHOW_VERSION "9.10" + +#include "client_priv.h" +#include +#include +#include +#include +#include +#include +#include +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +static char * host=0, *opt_password=0, *user=0; +static my_bool opt_show_keys= 0, opt_compress= 0, opt_count=0, opt_status= 0; +static my_bool tty_password= 0, opt_table_type= 0; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static uint my_end_arg= 0; +static uint opt_verbose=0; +static char *default_charset= (char*) MYSQL_AUTODETECT_CHARSET_NAME; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; + +static uint opt_protocol=0; + +static void get_options(int *argc,char ***argv); +static uint opt_mysql_port=0; +static int list_dbs(MYSQL *mysql,const char *wild); +static int list_tables(MYSQL *mysql,const char *db,const char *table); +static int list_table_status(MYSQL *mysql,const char *db,const char *table); +static int list_fields(MYSQL *mysql,const char *db,const char *table, + const char *field); +static void print_header(const char *header,size_t head_length,...); +static void print_row(const char *header,size_t head_length,...); +static void print_trailer(size_t length,...); +static void print_res_header(MYSQL_RES *result); +static void print_res_top(MYSQL_RES *result); +static void print_res_row(MYSQL_RES *result,MYSQL_ROW cur); + +static const char *load_default_groups[]= +{ "mysqlshow", "mariadb-show", "client", "client-server", "client-mariadb", + 0 }; +static char * opt_mysql_unix_port=0; + +int main(int argc, char **argv) +{ + int error; + my_bool first_argument_uses_wildcards=0; + char *wild; + MYSQL mysql; + my_bool reconnect; + static char **defaults_argv; + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv=argv; + + get_options(&argc,&argv); + + sf_leaking_memory=0; /* from now on we cleanup properly */ + wild=0; + if (argc) + { + char *pos= argv[argc-1], *to; + for (to= pos ; *pos ; pos++, to++) + { + switch (*pos) { + case '*': + *pos= '%'; + first_argument_uses_wildcards= 1; + break; + case '?': + *pos= '_'; + first_argument_uses_wildcards= 1; + break; + case '%': + case '_': + first_argument_uses_wildcards= 1; + break; + case '\\': + pos++; + default: break; + } + *to= *pos; + } + *to= *pos; /* just to copy a '\0' if '\\' was used */ + } + if (first_argument_uses_wildcards) + wild= argv[--argc]; + else if (argc == 3) /* We only want one field */ + wild= argv[--argc]; + + if (argc > 2) + { + fprintf(stderr,"%s: Too many arguments\n",my_progname); + exit(1); + } + mysql_init(&mysql); + if (opt_compress) + mysql_options(&mysql,MYSQL_OPT_COMPRESS,NullS); +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(&mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(&mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(&mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(&mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(&mysql,MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(&mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + + if (!strcmp(default_charset,MYSQL_AUTODETECT_CHARSET_NAME)) + default_charset= (char *)my_default_csname(); + my_set_console_cp(default_charset); + mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, default_charset); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(&mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(&mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqlshow"); + if (!(mysql_real_connect(&mysql,host,user,opt_password, + (first_argument_uses_wildcards) ? "" : + argv[0],opt_mysql_port,opt_mysql_unix_port, + 0))) + { + fprintf(stderr,"%s: %s\n",my_progname,mysql_error(&mysql)); + error= 1; + goto error; + } + reconnect= 1; + mysql_options(&mysql, MYSQL_OPT_RECONNECT, &reconnect); + + switch (argc) { + case 0: error=list_dbs(&mysql,wild); break; + case 1: + if (opt_status) + error=list_table_status(&mysql,argv[0],wild); + else + error=list_tables(&mysql,argv[0],wild); + break; + default: + if (opt_status && ! wild) + error=list_table_status(&mysql,argv[0],argv[1]); + else + error=list_fields(&mysql,argv[0],argv[1],wild); + break; + } +error: + mysql_close(&mysql); /* Close & free connection */ + my_free(opt_password); + mysql_server_end(); + free_defaults(defaults_argv); + my_end(my_end_arg); + exit(error ? 1 : 0); + return 0; /* No compiler warnings */ +} + +static struct my_option my_long_options[] = +{ + {"character-sets-dir", 'c', "Directory for character set files.", + (char**) &charsets_dir, (char**) &charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, + 0, 0, 0, 0, 0}, + {"default-character-set", OPT_DEFAULT_CHARSET, + "Set the default character set.", &default_charset, + &default_charset, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"count", OPT_COUNT, + "Show number of rows per table (may be slow for non-MyISAM tables).", + &opt_count, &opt_count, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", OPT_DEBUG_INFO, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", &host, &host, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"status", 'i', "Shows a lot of extra information about each table.", + &opt_status, &opt_status, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, + 0, 0}, + {"keys", 'k', "Show keys for table.", &opt_show_keys, + &opt_show_keys, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given, it's " + "solicited on the tty.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"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, 0, GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, + 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"show-table-type", 't', "Show table type column.", + &opt_table_type, &opt_table_type, 0, GET_BOOL, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", &user, + &user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', + "More verbose output; you can use this multiple times to get even more " + "verbose output.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname,SHOW_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + + +static void usage(void) +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + puts("Shows the structure of a MariaDB database (databases, tables, and columns).\n"); + printf("Usage: %s [OPTIONS] [database [table [column]]]\n",my_progname); + puts("\n\ +If last argument contains a shell or SQL wildcard (*,?,% or _) then only\n\ +what\'s matched by the wildcard is shown.\n\ +If no database is given then all matching databases are shown.\n\ +If no table is given, then all matching tables in database are shown.\n\ +If no column is given, then all matching columns and column types in table\n\ +are shown."); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +static my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + + switch(opt->id) { + case 'v': + opt_verbose++; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password=my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]=0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password=1; + break; + case 'W': +#ifdef _WIN32 + opt_protocol = MYSQL_PROTOCOL_PIPE; +#endif + break; + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + break; + case '#': + DBUG_PUSH(argument ? argument : "d:t:o"); + debug_check_flag= 1; + break; +#include + case 'V': + print_version(); + exit(0); + break; + case '?': + case 'I': /* Info */ + usage(); + exit(0); + } + return 0; +} + + +static void +get_options(int *argc,char ***argv) +{ + int ho_error; + + if ((ho_error=handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + + if (tty_password) + opt_password=my_get_tty_password(NullS); + if (opt_count) + { + /* + We need to set verbose to 2 as we need to change the output to include + the number-of-rows column + */ + opt_verbose= 2; + } + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + return; +} + + +static int +list_dbs(MYSQL *mysql,const char *wild) +{ + const char *header; + size_t length = 0; + uint counter = 0; + ulong rowcount = 0L; + char tables[NAME_LEN+1], rows[NAME_LEN+1]; + char query[NAME_LEN + 100]; + MYSQL_FIELD *field; + MYSQL_RES *result; + MYSQL_ROW row= NULL, rrow; + + if (!(result=mysql_list_dbs(mysql,wild))) + { + fprintf(stderr,"%s: Cannot list databases: %s\n",my_progname, + mysql_error(mysql)); + return 1; + } + + /* + If a wildcard was used, but there was only one row and it's name is an + exact match, we'll assume they really wanted to see the contents of that + database. This is because it is fairly common for database names to + contain the underscore (_), like INFORMATION_SCHEMA. + */ + if (wild && mysql_num_rows(result) == 1) + { + row= mysql_fetch_row(result); + if (!my_strcasecmp(&my_charset_latin1, row[0], wild)) + { + mysql_free_result(result); + if (opt_status) + return list_table_status(mysql, wild, NULL); + else + return list_tables(mysql, wild, NULL); + } + } + + if (wild) + printf("Wildcard: %s\n",wild); + + header="Databases"; + length= strlen(header); + field=mysql_fetch_field(result); + if (length < field->max_length) + length=field->max_length; + + if (!opt_verbose) + print_header(header,length,NullS); + else if (opt_verbose == 1) + print_header(header,length,"Tables",6,NullS); + else + print_header(header,length,"Tables",6,"Total Rows",12,NullS); + + /* The first row may have already been read up above. */ + while (row || (row= mysql_fetch_row(result))) + { + counter++; + + if (opt_verbose) + { + if (!(mysql_select_db(mysql,row[0]))) + { + MYSQL_RES *tresult = mysql_list_tables(mysql,(char*)NULL); + if (mysql_affected_rows(mysql) > 0) + { + snprintf(tables, sizeof(tables), "%6lu",(ulong) mysql_affected_rows(mysql)); + rowcount = 0; + if (opt_verbose > 1) + { + /* Print the count of tables and rows for each database */ + MYSQL_ROW trow; + while ((trow = mysql_fetch_row(tresult))) + { + my_snprintf(query, sizeof(query), + "SELECT COUNT(*) FROM `%s`", trow[0]); + if (!(mysql_query(mysql,query))) + { + MYSQL_RES *rresult; + if ((rresult = mysql_store_result(mysql))) + { + rrow = mysql_fetch_row(rresult); + rowcount += (ulong) strtoull(rrow[0], (char**) 0, 10); + mysql_free_result(rresult); + } + } + } + snprintf(rows, sizeof(rows), "%12lu", rowcount); + } + } + else + { + snprintf(tables, sizeof(tables), "%6d" ,0); + snprintf(rows, sizeof(rows), "%12d", 0); + } + mysql_free_result(tresult); + } + else + { + strmov(tables,"N/A"); + strmov(rows,"N/A"); + } + } + + if (!opt_verbose) + print_row(row[0],length,0); + else if (opt_verbose == 1) + print_row(row[0],length,tables,6,NullS); + else + print_row(row[0],length,tables,6,rows,12,NullS); + + row= NULL; + } + + print_trailer(length, + (opt_verbose > 0 ? 6 : 0), + (opt_verbose > 1 ? 12 :0), + 0); + + if (counter && opt_verbose) + printf("%u row%s in set.\n",counter,(counter > 1) ? "s" : ""); + mysql_free_result(result); + return 0; +} + + +static int +list_tables(MYSQL *mysql,const char *db,const char *table) +{ + const char *header; + size_t head_length; + uint counter = 0; + char query[NAME_LEN + 100], rows[NAME_LEN], fields[16]; + MYSQL_FIELD *field; + MYSQL_RES *result; + MYSQL_ROW row, rrow; + + if (mysql_select_db(mysql,db)) + { + fprintf(stderr,"%s: Cannot connect to db %s: %s\n",my_progname,db, + mysql_error(mysql)); + return 1; + } + if (table) + { + /* + We just hijack the 'rows' variable for a bit to store the escaped + table name + */ + mysql_real_escape_string(mysql, rows, table, (unsigned long)strlen(table)); + my_snprintf(query, sizeof(query), "show%s tables like '%s'", + opt_table_type ? " full" : "", rows); + } + else + my_snprintf(query, sizeof(query), "show%s tables", + opt_table_type ? " full" : ""); + if (mysql_query(mysql, query) || !(result= mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot list tables in %s: %s\n",my_progname,db, + mysql_error(mysql)); + exit(1); + } + printf("Database: %s",db); + if (table) + printf(" Wildcard: %s",table); + putchar('\n'); + + header="Tables"; + head_length= strlen(header); + field=mysql_fetch_field(result); + if (head_length < field->max_length) + head_length=field->max_length; + + if (opt_table_type) + { + if (!opt_verbose) + print_header(header,head_length,"table_type",10,NullS); + else if (opt_verbose == 1) + print_header(header,head_length,"table_type",10,"Columns",8,NullS); + else + { + print_header(header,head_length,"table_type",10,"Columns",8, + "Total Rows",10,NullS); + } + } + else + { + if (!opt_verbose) + print_header(header,head_length,NullS); + else if (opt_verbose == 1) + print_header(header,head_length,"Columns",8,NullS); + else + print_header(header,head_length,"Columns",8, "Total Rows",10,NullS); + } + + while ((row = mysql_fetch_row(result))) + { + counter++; + if (opt_verbose > 0) + { + if (!(mysql_select_db(mysql,db))) + { + MYSQL_RES *rresult = mysql_list_fields(mysql,row[0],NULL); + ulong rowcount=0L; + if (!rresult) + { + strmov(fields,"N/A"); + strmov(rows,"N/A"); + } + else + { + snprintf(fields, sizeof(fields), "%8u", (uint) mysql_num_fields(rresult)); + mysql_free_result(rresult); + + if (opt_verbose > 1) + { + /* Print the count of rows for each table */ + my_snprintf(query, sizeof(query), "SELECT COUNT(*) FROM `%s`", + row[0]); + if (!(mysql_query(mysql,query))) + { + if ((rresult = mysql_store_result(mysql))) + { + rrow = mysql_fetch_row(rresult); + rowcount += (unsigned long) strtoull(rrow[0], (char**) 0, 10); + mysql_free_result(rresult); + } + snprintf(rows, sizeof(rows), "%10lu", rowcount); + } + else + snprintf(rows, sizeof(rows), "%10d", 0); + } + } + } + else + { + strmov(fields,"N/A"); + strmov(rows,"N/A"); + } + } + if (opt_table_type) + { + if (!opt_verbose) + print_row(row[0],head_length,row[1],10,NullS); + else if (opt_verbose == 1) + print_row(row[0],head_length,row[1],10,fields,8,NullS); + else + print_row(row[0],head_length,row[1],10,fields,8,rows,10,NullS); + } + else + { + if (!opt_verbose) + print_row(row[0],head_length,NullS); + else if (opt_verbose == 1) + print_row(row[0],head_length, fields,8, NullS); + else + print_row(row[0],head_length, fields,8, rows,10, NullS); + } + } + + print_trailer(head_length, + (opt_table_type ? 10 : opt_verbose > 0 ? 8 : 0), + (opt_table_type ? (opt_verbose > 0 ? 8 : 0) + : (opt_verbose > 1 ? 10 :0)), + !opt_table_type ? 0 : opt_verbose > 1 ? 10 :0, + 0); + + if (counter && opt_verbose) + printf("%u row%s in set.\n\n",counter,(counter > 1) ? "s" : ""); + + mysql_free_result(result); + return 0; +} + + +static int +list_table_status(MYSQL *mysql,const char *db,const char *wild) +{ + char query[NAME_LEN + 100]; + size_t len; + MYSQL_RES *result; + MYSQL_ROW row; + + len= sizeof(query); + len-= my_snprintf(query, len, "show table status from `%s`", db); + if (wild && wild[0] && len) + strxnmov(query + strlen(query), len - 1, " like '", wild, "'", NullS); + if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot get status for db: %s, table: %s: %s\n", + my_progname,db,wild ? wild : "",mysql_error(mysql)); + if (mysql_errno(mysql) == ER_PARSE_ERROR) + fprintf(stderr,"This error probably means that your MariaDB server doesn't support the\n\'show table status' command.\n"); + return 1; + } + + printf("Database: %s",db); + if (wild) + printf(" Wildcard: %s",wild); + putchar('\n'); + + print_res_header(result); + while ((row=mysql_fetch_row(result))) + print_res_row(result,row); + print_res_top(result); + mysql_free_result(result); + return 0; +} + +/* + list fields uses field interface as an example of how to parse + a MYSQL FIELD +*/ + +static int +list_fields(MYSQL *mysql,const char *db,const char *table, + const char *wild) +{ + char query[NAME_LEN + 100]; + size_t len; + MYSQL_RES *result; + MYSQL_ROW row; + ulong UNINIT_VAR(rows); + + if (mysql_select_db(mysql,db)) + { + fprintf(stderr,"%s: Cannot connect to db: %s: %s\n",my_progname,db, + mysql_error(mysql)); + return 1; + } + + if (opt_count) + { + my_snprintf(query, sizeof(query), "select count(*) from `%s`", table); + if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot get record count for db: %s, table: %s: %s\n", + my_progname,db,table,mysql_error(mysql)); + return 1; + } + row= mysql_fetch_row(result); + rows= (ulong) strtoull(row[0], (char**) 0, 10); + mysql_free_result(result); + } + + len= sizeof(query); + len-= my_snprintf(query, len, "show /*!32332 FULL */ columns from `%s`", + table); + if (wild && wild[0] && len) + strxnmov(query + strlen(query), len - 1, " like '", wild, "'", NullS); + if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot list columns in db: %s, table: %s: %s\n", + my_progname,db,table,mysql_error(mysql)); + return 1; + } + + printf("Database: %s Table: %s", db, table); + if (opt_count) + printf(" Rows: %lu", rows); + if (wild && wild[0]) + printf(" Wildcard: %s",wild); + putchar('\n'); + + print_res_header(result); + while ((row=mysql_fetch_row(result))) + print_res_row(result,row); + print_res_top(result); + if (opt_show_keys) + { + my_snprintf(query, sizeof(query), "show keys from `%s`", table); + if (mysql_query(mysql,query) || !(result=mysql_store_result(mysql))) + { + fprintf(stderr,"%s: Cannot list keys in db: %s, table: %s: %s\n", + my_progname,db,table,mysql_error(mysql)); + return 1; + } + if (mysql_num_rows(result)) + { + print_res_header(result); + while ((row=mysql_fetch_row(result))) + print_res_row(result,row); + print_res_top(result); + } + else + puts("Table has no keys"); + } + mysql_free_result(result); + return 0; +} + + +/***************************************************************************** + General functions to print a nice ascii-table from data +*****************************************************************************/ + +static void +print_header(const char *header,size_t head_length,...) +{ + va_list args; + size_t length,i,str_length,pre_space; + const char *field; + + va_start(args,head_length); + putchar('+'); + field=header; length=head_length; + for (;;) + { + for (i=0 ; i < length+2 ; i++) + putchar('-'); + putchar('+'); + if (!(field=va_arg(args,char *))) + break; + length=va_arg(args,uint); + } + va_end(args); + putchar('\n'); + + va_start(args,head_length); + field=header; length=head_length; + putchar('|'); + for (;;) + { + str_length= strlen(field); + if (str_length > length) + str_length=length+1; + pre_space= ((length- str_length)/2)+1; + for (i=0 ; i < pre_space ; i++) + putchar(' '); + for (i = 0 ; i < str_length ; i++) + putchar(field[i]); + length=length+2-str_length-pre_space; + for (i=0 ; i < length ; i++) + putchar(' '); + putchar('|'); + if (!(field=va_arg(args,char *))) + break; + length=va_arg(args,uint); + } + va_end(args); + putchar('\n'); + + va_start(args,head_length); + putchar('+'); + field=header; length=head_length; + for (;;) + { + for (i=0 ; i < length+2 ; i++) + putchar('-'); + putchar('+'); + if (!(field=va_arg(args,char *))) + break; + length=va_arg(args,uint); + } + va_end(args); + putchar('\n'); +} + + +static void +print_row(const char *header,size_t head_length,...) +{ + va_list args; + const char *field; + size_t i,length,field_length; + + va_start(args,head_length); + field=header; length=head_length; + for (;;) + { + putchar('|'); + putchar(' '); + fputs(field,stdout); + field_length= strlen(field); + for (i=field_length ; i <= length ; i++) + putchar(' '); + if (!(field=va_arg(args,char *))) + break; + length=va_arg(args,uint); + } + va_end(args); + putchar('|'); + putchar('\n'); +} + + +static void +print_trailer(size_t head_length,...) +{ + va_list args; + size_t length,i; + + va_start(args,head_length); + length=head_length; + putchar('+'); + for (;;) + { + for (i=0 ; i < length+2 ; i++) + putchar('-'); + putchar('+'); + if (!(length=va_arg(args,uint))) + break; + } + va_end(args); + putchar('\n'); +} + + +static void print_res_header(MYSQL_RES *result) +{ + MYSQL_FIELD *field; + + print_res_top(result); + mysql_field_seek(result,0); + putchar('|'); + while ((field = mysql_fetch_field(result))) + { + printf(" %-*s|",(int) field->max_length+1,field->name); + } + putchar('\n'); + print_res_top(result); +} + + +static void print_res_top(MYSQL_RES *result) +{ + size_t i,length; + MYSQL_FIELD *field; + + putchar('+'); + mysql_field_seek(result,0); + while((field = mysql_fetch_field(result))) + { + if ((length= strlen(field->name)) > field->max_length) + field->max_length=(ulong)length; + else + length=field->max_length; + for (i=length+2 ; i--> 0 ; ) + putchar('-'); + putchar('+'); + } + putchar('\n'); +} + + +static void print_res_row(MYSQL_RES *result,MYSQL_ROW cur) +{ + uint i,length; + MYSQL_FIELD *field; + putchar('|'); + mysql_field_seek(result,0); + for (i=0 ; i < mysql_num_fields(result); i++) + { + field = mysql_fetch_field(result); + length=field->max_length; + printf(" %-*s|",length+1,cur[i] ? (char*) cur[i] : ""); + } + putchar('\n'); +} diff --git a/client/mysqlslap.c b/client/mysqlslap.c new file mode 100644 index 00000000..885a5f43 --- /dev/null +++ b/client/mysqlslap.c @@ -0,0 +1,2331 @@ +/* + Copyright (c) 2005, 2015, Oracle and/or its affiliates. + Copyright (c) 2010, 2022, MariaDB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; version 2 of the License. + + 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 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-1335 USA +*/ + +/* + MySQL Slap + + A simple program designed to work as if multiple clients querying the database, + then reporting the timing of each stage. + + MySQL slap runs three stages: + 1) Create schema,table, and optionally any SP or data you want to begin + the test with. (single client) + 2) Load test (many clients) + 3) Cleanup (disconnection, drop table if specified, single client) + + Examples: + + Supply your own create and query SQL statements, with 50 clients + querying (200 selects for each): + + mysqlslap --delimiter=";" \ + --create="CREATE TABLE A (a int);INSERT INTO A VALUES (23)" \ + --query="SELECT * FROM A" --concurrency=50 --iterations=200 + + Let the program build the query SQL statement with a table of two int + columns, three varchar columns, five clients querying (20 times each), + don't create the table or insert the data (using the previous test's + schema and data): + + mysqlslap --concurrency=5 --iterations=20 \ + --number-int-cols=2 --number-char-cols=3 \ + --auto-generate-sql + + Tell the program to load the create, insert and query SQL statements from + the specified files, where the create.sql file has multiple table creation + statements delimited by ';' and multiple insert statements delimited by ';'. + The --query file will have multiple queries delimited by ';', run all the + load statements, and then run all the queries in the query file + with five clients (five times each): + + mysqlslap --concurrency=5 \ + --iterations=5 --query=query.sql --create=create.sql \ + --delimiter=";" + +TODO: + Add language for better tests + String length for files and those put on the command line are not + setup to handle binary data. + More stats + Break up tests and run them on multiple hosts at once. + Allow output to be fed into a database directly. + +*/ + +#define SLAP_VERSION "1.0" + +#define HUGE_STRING_LENGTH 8196 +#define RAND_STRING_SIZE 126 + +/* Types */ +#define SELECT_TYPE 0 +#define UPDATE_TYPE 1 +#define INSERT_TYPE 2 +#define UPDATE_TYPE_REQUIRES_PREFIX 3 +#define CREATE_TABLE_TYPE 4 +#define SELECT_TYPE_REQUIRES_PREFIX 5 +#define DELETE_TYPE_REQUIRES_PREFIX 6 + +#include "client_priv.h" +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#include /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +#ifdef _WIN32 +#define srandom srand +#define random() (long)rand() +#endif + + +/* Global Thread counter */ +uint thread_counter; +pthread_mutex_t counter_mutex; +pthread_cond_t count_threshhold; +uint master_wakeup; +pthread_mutex_t sleeper_mutex; +pthread_cond_t sleep_threshhold; + +static char **defaults_argv; + +char **primary_keys; +unsigned long long primary_keys_number_of; + +static char *host= NULL, *opt_password= NULL, *user= NULL, + *user_supplied_query= NULL, + *user_supplied_pre_statements= NULL, + *user_supplied_post_statements= NULL, + *default_engine= NULL, + *pre_system= NULL, + *post_system= NULL, + *opt_mysql_unix_port= NULL, + *opt_init_command= NULL; +static char *opt_plugin_dir= 0, *opt_default_auth= 0; + +const char *delimiter= "\n"; + +const char *create_schema_string= "mysqlslap"; + +static my_bool opt_preserve= TRUE, opt_no_drop= FALSE; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static my_bool opt_only_print= FALSE; +static my_bool opt_compress= FALSE, tty_password= FALSE, + opt_silent= FALSE, + auto_generate_sql_autoincrement= FALSE, + auto_generate_sql_guid_primary= FALSE, + auto_generate_sql= FALSE; +const char *auto_generate_sql_type= "mixed"; + +static unsigned long connect_flags= CLIENT_MULTI_RESULTS | + CLIENT_MULTI_STATEMENTS | + CLIENT_REMEMBER_OPTIONS; + +static int verbose; +static uint commit_rate; +static uint detach_rate; +const char *num_int_cols_opt; +const char *num_char_cols_opt; + +/* Yes, we do set defaults here */ +static unsigned int num_int_cols= 1; +static unsigned int num_char_cols= 1; +static unsigned int num_int_cols_index= 0; +static unsigned int num_char_cols_index= 0; +static unsigned int iterations; +static uint my_end_arg= 0; +static char *default_charset= (char*) MYSQL_DEFAULT_CHARSET_NAME; +static ulonglong actual_queries= 0; +static ulonglong auto_actual_queries; +static ulonglong auto_generate_sql_unique_write_number; +static ulonglong auto_generate_sql_unique_query_number; +static unsigned int auto_generate_sql_secondary_indexes; +static ulonglong num_of_query; +static ulonglong auto_generate_sql_number; +const char *concurrency_str= NULL; +static char *create_string; +uint *concurrency; +static char mysql_charsets_dir[FN_REFLEN+1]; + +const char *default_dbug_option="d:t:o,/tmp/mariadb-slap.trace"; +const char *opt_csv_str; +File csv_file; + +static uint opt_protocol= 0; + +static int get_options(int *argc,char ***argv); +static uint opt_mysql_port= 0; + +static const char *load_default_groups[]= +{ "mysqlslap", "mariadb-slap", "client", "client-server", "client-mariadb", + 0 }; + +typedef struct statement statement; + +struct statement { + char *string; + size_t length; + unsigned char type; + char *option; + size_t option_length; + statement *next; +}; + +typedef struct option_string option_string; + +struct option_string { + char *string; + size_t length; + char *option; + size_t option_length; + option_string *next; +}; + +typedef struct stats stats; + +struct stats { + long int timing; + uint users; + unsigned long long rows; +}; + +typedef struct thread_context thread_context; + +struct thread_context { + statement *stmt; + ulonglong limit; +}; + +typedef struct conclusions conclusions; + +struct conclusions { + char *engine; + long int avg_timing; + long int max_timing; + long int min_timing; + uint users; + unsigned long long avg_rows; + /* The following are not used yet */ + unsigned long long max_rows; + unsigned long long min_rows; +}; + +static option_string *engine_options= NULL; +static statement *pre_statements= NULL; +static statement *post_statements= NULL; +static statement *create_statements= NULL, + *query_statements= NULL; + +/* Prototypes */ +void print_conclusions(conclusions *con); +void print_conclusions_csv(conclusions *con); +void generate_stats(conclusions *con, option_string *eng, stats *sptr); +uint parse_comma(const char *string, uint **range); +uint parse_delimiter(const char *script, statement **stmt, char delm); +int parse_option(const char *origin, option_string **stmt, char delm); +static int drop_schema(MYSQL *mysql, const char *db); +uint get_random_string(char *buf); +static statement *build_table_string(void); +static statement *build_insert_string(void); +static statement *build_update_string(void); +static statement * build_select_string(my_bool key); +static int generate_primary_key_list(MYSQL *mysql, option_string *engine_stmt); +static int drop_primary_key_list(void); +static int create_schema(MYSQL *mysql, const char *db, statement *stmt, + option_string *engine_stmt); +static int run_scheduler(stats *sptr, statement *stmts, uint concur, + ulonglong limit); +pthread_handler_t run_task(void *p); +void statement_cleanup(statement *stmt); +void option_cleanup(option_string *stmt); +void concurrency_loop(MYSQL *mysql, uint current, option_string *eptr); +static int run_statements(MYSQL *mysql, statement *stmt); +int slap_connect(MYSQL *mysql); +static int run_query(MYSQL *mysql, const char *query, size_t len); + +static const char ALPHANUMERICS[]= + "0123456789ABCDEFGHIJKLMNOPQRSTWXYZabcdefghijklmnopqrstuvwxyz"; + +#define ALPHANUMERICS_SIZE (sizeof(ALPHANUMERICS)-1) + + +static long int timedif(struct timeval a, struct timeval b) +{ + register int us, s; + + us = a.tv_usec - b.tv_usec; + us /= 1000; + s = a.tv_sec - b.tv_sec; + s *= 1000; + return s + us; +} + +#ifdef _WIN32 +static int gettimeofday(struct timeval *tp, void *tzp) +{ + unsigned int ticks; + ticks= GetTickCount(); + tp->tv_usec= ticks*1000; + tp->tv_sec= ticks/1000; + + return 0; +} +#endif + +void set_mysql_connect_options(MYSQL *mysql) +{ + if (opt_compress) + mysql_options(mysql,MYSQL_OPT_COMPRESS,NullS); +#ifdef HAVE_OPENSSL + if (opt_use_ssl) + { + mysql_ssl_set(mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + } + mysql_options(mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + (char*)&opt_ssl_verify_server_cert); +#endif + if (opt_protocol) + mysql_options(mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + mysql_options(mysql, MYSQL_SET_CHARSET_NAME, default_charset); +} + + +int main(int argc, char **argv) +{ + MYSQL mysql; + option_string *eptr; + + MY_INIT(argv[0]); + sf_leaking_memory=1; /* don't report memory leaks on early exits */ + + /* We need to know if protocol-related options originate from CLI args */ + my_defaults_mark_files = TRUE; + + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + defaults_argv=argv; + if (get_options(&argc,&argv)) + { + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + + sf_leaking_memory=0; /* from now on we cleanup properly */ + + /* Seed the random number generator if we will be using it. */ + if (auto_generate_sql) + srandom((uint)time(NULL)); + + if (argc > 2) + { + fprintf(stderr,"%s: Too many arguments\n",my_progname); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + mysql_init(&mysql); + set_mysql_connect_options(&mysql); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(&mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (opt_default_auth && *opt_default_auth) + mysql_options(&mysql, MYSQL_DEFAULT_AUTH, opt_default_auth); + + mysql_options(&mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(&mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqlslap"); + if (!opt_only_print) + { + if (!(mysql_real_connect(&mysql, host, user, opt_password, + NULL, opt_mysql_port, + opt_mysql_unix_port, connect_flags))) + { + fprintf(stderr,"%s: Error when connecting to server: %s\n", + my_progname,mysql_error(&mysql)); + mysql_close(&mysql); + free_defaults(defaults_argv); + my_end(0); + exit(1); + } + } + + pthread_mutex_init(&counter_mutex, NULL); + pthread_cond_init(&count_threshhold, NULL); + pthread_mutex_init(&sleeper_mutex, NULL); + pthread_cond_init(&sleep_threshhold, NULL); + + /* Main iterations loop */ + eptr= engine_options; + do + { + /* For the final stage we run whatever queries we were asked to run */ + uint *current; + + if (verbose >= 2) + printf("Starting Concurrency Test\n"); + + if (*concurrency) + { + for (current= concurrency; current && *current; current++) + concurrency_loop(&mysql, *current, eptr); + } + else + { + uint infinite= 1; + do { + concurrency_loop(&mysql, infinite, eptr); + } + while (infinite++); + } + + if (!opt_preserve) + drop_schema(&mysql, create_schema_string); + + } while (eptr ? (eptr= eptr->next) : 0); + + pthread_mutex_destroy(&counter_mutex); + pthread_cond_destroy(&count_threshhold); + pthread_mutex_destroy(&sleeper_mutex); + pthread_cond_destroy(&sleep_threshhold); + + mysql_close(&mysql); /* Close & free connection */ + + /* now free all the strings we created */ + my_free(opt_password); + my_free(concurrency); + + statement_cleanup(create_statements); + statement_cleanup(query_statements); + statement_cleanup(pre_statements); + statement_cleanup(post_statements); + option_cleanup(engine_options); + free_defaults(defaults_argv); + mysql_library_end(); + my_end(my_end_arg); + + return 0; +} + +void concurrency_loop(MYSQL *mysql, uint current, option_string *eptr) +{ + unsigned int x; + stats *head_sptr; + stats *sptr; + conclusions conclusion; + unsigned long long client_limit; + int sysret; + + head_sptr= (stats *)my_malloc(PSI_NOT_INSTRUMENTED, + sizeof(stats) * iterations, MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + + bzero(&conclusion, sizeof(conclusions)); + + if (auto_actual_queries) + client_limit= auto_actual_queries; + else if (num_of_query) + client_limit= num_of_query / current; + else + client_limit= actual_queries; + + for (x= 0, sptr= head_sptr; x < iterations; x++, sptr++) + { + /* + We might not want to load any data, such as when we are calling + a stored_procedure that doesn't use data, or we know we already have + data in the table. + */ + if (!opt_preserve) + drop_schema(mysql, create_schema_string); + + /* First we create */ + if (create_statements) + { + /* + If we have an --engine option, then we indicate + create_schema() to add the engine type to the DDL. + */ + if (eptr) + create_statements->type= CREATE_TABLE_TYPE; + + create_schema(mysql, create_schema_string, create_statements, eptr); + } + + /* + If we generated GUID we need to build a list of them from creation that + we can later use. + */ + if (verbose >= 2) + printf("Generating primary key list\n"); + if (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary) + generate_primary_key_list(mysql, eptr); + + if (commit_rate) + run_query(mysql, "SET AUTOCOMMIT=0", strlen("SET AUTOCOMMIT=0")); + + if (pre_system && (sysret= system(pre_system)) != 0) + fprintf(stderr, + "Warning: Execution of pre_system option returned %d.\n", + sysret); + + /* + Pre statements are always run after all other logic so they can + correct/adjust any item that they want. + */ + if (pre_statements) + run_statements(mysql, pre_statements); + + run_scheduler(sptr, query_statements, current, client_limit); + + if (post_statements) + run_statements(mysql, post_statements); + + if (post_system && (sysret= system(post_system)) != 0) + fprintf(stderr, + "Warning: Execution of post_system option returned %d.\n", + sysret); + /* We are finished with this run */ + if (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary) + drop_primary_key_list(); + } + + if (verbose >= 2) + printf("Generating stats\n"); + + generate_stats(&conclusion, eptr, head_sptr); + + if (!opt_silent) + print_conclusions(&conclusion); + if (opt_csv_str) + print_conclusions_csv(&conclusion); + + my_free(head_sptr); + +} + + +static struct my_option my_long_options[] = +{ + {"help", '?', "Display this help and exit.", 0, 0, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql", 'a', + "Generate SQL where not supplied by file or command line.", + &auto_generate_sql, &auto_generate_sql, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-add-autoincrement", OPT_SLAP_AUTO_GENERATE_ADD_AUTO, + "Add an AUTO_INCREMENT column to auto-generated tables.", + &auto_generate_sql_autoincrement, + &auto_generate_sql_autoincrement, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-execute-number", OPT_SLAP_AUTO_GENERATE_EXECUTE_QUERIES, + "Set this number to generate a set number of queries to run.", + &auto_actual_queries, &auto_actual_queries, + 0, GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-guid-primary", OPT_SLAP_AUTO_GENERATE_GUID_PRIMARY, + "Add GUID based primary keys to auto-generated tables.", + &auto_generate_sql_guid_primary, + &auto_generate_sql_guid_primary, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-load-type", OPT_SLAP_AUTO_GENERATE_SQL_LOAD_TYPE, + "Specify test load type: mixed, update, write, key, or read; default is mixed.", + (char**) &auto_generate_sql_type, (char**) &auto_generate_sql_type, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-secondary-indexes", + OPT_SLAP_AUTO_GENERATE_SECONDARY_INDEXES, + "Number of secondary indexes to add to auto-generated tables.", + &auto_generate_sql_secondary_indexes, + &auto_generate_sql_secondary_indexes, 0, + GET_UINT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"auto-generate-sql-unique-query-number", + OPT_SLAP_AUTO_GENERATE_UNIQUE_QUERY_NUM, + "Number of unique queries to generate for automatic tests.", + &auto_generate_sql_unique_query_number, + &auto_generate_sql_unique_query_number, + 0, GET_ULL, REQUIRED_ARG, 10, 0, 0, 0, 0, 0}, + {"auto-generate-sql-unique-write-number", + OPT_SLAP_AUTO_GENERATE_UNIQUE_WRITE_NUM, + "Number of unique queries to generate for auto-generate-sql-write-number.", + &auto_generate_sql_unique_write_number, + &auto_generate_sql_unique_write_number, + 0, GET_ULL, REQUIRED_ARG, 10, 0, 0, 0, 0, 0}, + {"auto-generate-sql-write-number", OPT_SLAP_AUTO_GENERATE_WRITE_NUM, + "Number of row inserts to perform for each thread (default is 100).", + &auto_generate_sql_number, &auto_generate_sql_number, + 0, GET_ULL, REQUIRED_ARG, 100, 0, 0, 0, 0, 0}, + {"character-sets-dir", OPT_CHARSETS_DIR, + "Directory for character set files.", (char **)&charsets_dir, + (char **)&charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"commit", OPT_SLAP_COMMIT, "Commit records every X number of statements.", + &commit_rate, &commit_rate, 0, GET_UINT, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use compression in server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"concurrency", 'c', "Number of clients to simulate for query to run.", + (char**) &concurrency_str, (char**) &concurrency_str, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"create", OPT_SLAP_CREATE_STRING, "File or string to use create tables.", + &create_string, &create_string, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"create-schema", OPT_CREATE_SLAP_SCHEMA, "Schema to run tests in.", + (char**) &create_schema_string, (char**) &create_schema_string, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"csv", OPT_SLAP_CSV, + "Generate CSV output to named file or to stdout if no file is named.", + NULL, NULL, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef DBUG_OFF + {"debug", '#', "This is a non-debug version. Catch this and exit.", + 0, 0, 0, GET_DISABLED, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#else + {"debug", '#', "Output debug log. Often this is 'd:t:o,filename'.", + (char**) &default_dbug_option, (char**) &default_dbug_option, 0, GET_STR, + OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", OPT_DEBUG_CHECK, "Check memory and open file usage at exit.", + &debug_check_flag, &debug_check_flag, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"debug-info", 'T', "Print some debug info at exit.", &debug_info_flag, + &debug_info_flag, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"default_auth", OPT_DEFAULT_AUTH, + "Default authentication client-side plugin to use.", + &opt_default_auth, &opt_default_auth, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"delimiter", 'F', + "Delimiter to use in SQL statements supplied in file or command line.", + (char**) &delimiter, (char**) &delimiter, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"detach", OPT_SLAP_DETACH, + "Detach (close and reopen) connections after X number of requests.", + &detach_rate, &detach_rate, 0, GET_UINT, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"engine", 'e', + "Comma separated list of storage engines to use for creating the table." + " The test is run for each engine. You can also specify an option for an " + "engine after a `:', like memory:max_row=2300", + &default_engine, &default_engine, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", &host, &host, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"init-command", OPT_INIT_COMMAND, + "SQL Command to execute when connecting to MariaDB server. Will " + "automatically be re-executed when reconnecting.", + &opt_init_command, &opt_init_command, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"iterations", 'i', "Number of times to run the tests.", &iterations, + &iterations, 0, GET_UINT, REQUIRED_ARG, 1, 0, 0, 0, 0, 0}, + {"no-drop", OPT_SLAP_NO_DROP, "Do not drop the schema after the test.", + &opt_no_drop, &opt_no_drop, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"number-char-cols", 'x', + "Number of VARCHAR columns to create in table if specifying --auto-generate-sql.", + (char**) &num_char_cols_opt, (char**) &num_char_cols_opt, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"number-int-cols", 'y', + "Number of INT columns to create in table if specifying --auto-generate-sql.", + (char**) &num_int_cols_opt, (char**) &num_int_cols_opt, 0, GET_STR, REQUIRED_ARG, + 0, 0, 0, 0, 0, 0}, + {"number-of-queries", OPT_MYSQL_NUMBER_OF_QUERY, + "Limit each client to this number of queries (this is not exact).", + &num_of_query, &num_of_query, 0, + GET_ULL, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"only-print", OPT_MYSQL_ONLY_PRINT, + "Do not connect to the databases, but instead print out what would have " + "been done.", + &opt_only_print, &opt_only_print, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"password", 'p', + "Password to use when connecting to server. If password is not given it's " + "asked from the tty.", 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#ifdef _WIN32 + {"pipe", 'W', "Use named pipes to connect to server.", 0, 0, 0, GET_NO_ARG, + NO_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"plugin_dir", OPT_PLUGIN_DIR, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"port", 'P', "Port number to use for connection.", &opt_mysql_port, + &opt_mysql_port, 0, GET_UINT, REQUIRED_ARG, MYSQL_PORT, 0, 0, 0, 0, + 0}, + {"post-query", OPT_SLAP_POST_QUERY, + "Query to run or file containing query to execute after tests have completed.", + &user_supplied_post_statements, &user_supplied_post_statements, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"post-system", OPT_SLAP_POST_SYSTEM, + "system() string to execute after tests have completed.", + &post_system, &post_system, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"pre-query", OPT_SLAP_PRE_QUERY, + "Query to run or file containing query to execute before running tests.", + &user_supplied_pre_statements, &user_supplied_pre_statements, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"pre-system", OPT_SLAP_PRE_SYSTEM, + "system() string to execute before running tests.", + &pre_system, &pre_system, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, + "The protocol to use for connection (tcp, socket, pipe).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"query", 'q', "Query to run or file containing query to run.", + &user_supplied_query, &user_supplied_query, + 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Run program in silent mode - no output.", + &opt_silent, &opt_silent, 0, GET_BOOL, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &opt_mysql_unix_port, &opt_mysql_unix_port, 0, GET_STR, + REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#include +#ifndef DONT_ALLOW_USER_CHANGE + {"user", 'u', "User for login if not current user.", &user, + &user, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"verbose", 'v', + "More verbose output; you can use this multiple times to get even more " + "verbose output.", &verbose, &verbose, 0, GET_NO_ARG, NO_ARG, + 0, 0, 0, 0, 0, 0}, + {"version", 'V', "Output version information and exit.", 0, 0, 0, + GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} +}; + + +static void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname, SLAP_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + + +static void usage(void) +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2005")); + puts("Run a query multiple times against the server.\n"); + printf("Usage: %s [OPTIONS]\n",my_progname); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +static my_bool +get_one_option(const struct my_option *opt, const char *argument, + const char *filename) +{ + DBUG_ENTER("get_one_option"); + switch(opt->id) { + case 'v': + verbose++; + break; + case 'p': + if (argument == disabled_my_option) + argument= (char*) ""; /* Don't require password */ + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + char *start= (char*) argument; + my_free(opt_password); + opt_password= my_strdup(PSI_NOT_INSTRUMENTED, argument,MYF(MY_FAE)); + while (*argument) + *(char*) argument++= 'x'; /* Destroy argument */ + if (*start) + start[1]= 0; /* Cut length of argument */ + tty_password= 0; + } + else + tty_password= 1; + break; + case 'W': +#ifdef _WIN32 + opt_protocol= MYSQL_PROTOCOL_PIPE; +#endif + break; + case OPT_MYSQL_PROTOCOL: + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + { + sf_leaking_memory= 1; /* no memory leak reports here */ + exit(1); + } + break; + case 'P': + if (filename[0] == '\0') + { + /* Port given on command line, switch protocol to use TCP */ + opt_protocol= MYSQL_PROTOCOL_TCP; + } + break; + case 'S': + if (filename[0] == '\0') + { + /* + Socket given on command line, switch protocol to use SOCKETSt + Except on Windows if 'protocol= pipe' has been provided in + the config file or command line. + */ + if (opt_protocol != MYSQL_PROTOCOL_PIPE) + { + opt_protocol= MYSQL_PROTOCOL_SOCKET; + } + } + break; + case '#': + DBUG_PUSH(argument ? argument : default_dbug_option); + debug_check_flag= 1; + break; + case OPT_CHARSETS_DIR: + strmake_buf(mysql_charsets_dir, argument); + charsets_dir = mysql_charsets_dir; + break; + case OPT_SLAP_CSV: + if (!argument) + argument= (char *)"-"; /* use stdout */ + opt_csv_str= argument; + break; +#include + case 'V': + print_version(); + exit(0); + break; + case '?': + case 'I': /* Info */ + usage(); + exit(0); + } + DBUG_RETURN(0); +} + + +uint +get_random_string(char *buf) +{ + char *buf_ptr= buf; + int x; + DBUG_ENTER("get_random_string"); + for (x= RAND_STRING_SIZE; x > 0; x--) + *buf_ptr++= ALPHANUMERICS[random() % ALPHANUMERICS_SIZE]; + DBUG_RETURN((uint)(buf_ptr - buf)); +} + + +/* + build_table_string + + This function builds a create table query if the user opts to not supply + a file or string containing a create table statement +*/ +static statement * +build_table_string(void) +{ + char buf[HUGE_STRING_LENGTH]; + unsigned int col_count; + statement *ptr; + DYNAMIC_STRING table_string; + DBUG_ENTER("build_table_string"); + + DBUG_PRINT("info", ("num int cols %u num char cols %u", + num_int_cols, num_char_cols)); + + init_dynamic_string(&table_string, "", 1024, 1024); + + dynstr_append(&table_string, "CREATE TABLE `t1` ("); + + if (auto_generate_sql_autoincrement) + { + dynstr_append(&table_string, "id serial"); + + if (num_int_cols || num_char_cols) + dynstr_append(&table_string, ","); + } + + if (auto_generate_sql_guid_primary) + { + dynstr_append(&table_string, "id varchar(36) primary key"); + + if (num_int_cols || num_char_cols || auto_generate_sql_guid_primary) + dynstr_append(&table_string, ","); + } + + if (auto_generate_sql_secondary_indexes) + { + unsigned int count; + + for (count= 0; count < auto_generate_sql_secondary_indexes; count++) + { + if (count) /* Except for the first pass we add a comma */ + dynstr_append(&table_string, ","); + + if (snprintf(buf, HUGE_STRING_LENGTH, "id%d varchar(36) unique key", count) + > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in create table\n"); + exit(1); + } + dynstr_append(&table_string, buf); + } + + if (num_int_cols || num_char_cols) + dynstr_append(&table_string, ","); + } + + if (num_int_cols) + for (col_count= 1; col_count <= num_int_cols; col_count++) + { + if (num_int_cols_index) + { + if (snprintf(buf, HUGE_STRING_LENGTH, "intcol%d INT(32), INDEX(intcol%d)", + col_count, col_count) > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in create table\n"); + exit(1); + } + } + else + { + if (snprintf(buf, HUGE_STRING_LENGTH, "intcol%d INT(32) ", col_count) + > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in create table\n"); + exit(1); + } + } + dynstr_append(&table_string, buf); + + if (col_count < num_int_cols || num_char_cols > 0) + dynstr_append(&table_string, ","); + } + + if (num_char_cols) + for (col_count= 1; col_count <= num_char_cols; col_count++) + { + if (num_char_cols_index) + { + if (snprintf(buf, HUGE_STRING_LENGTH, + "charcol%d VARCHAR(128), INDEX(charcol%d) ", + col_count, col_count) > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating table\n"); + exit(1); + } + } + else + { + if (snprintf(buf, HUGE_STRING_LENGTH, "charcol%d VARCHAR(128)", + col_count) > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating table\n"); + exit(1); + } + } + dynstr_append(&table_string, buf); + + if (col_count < num_char_cols) + dynstr_append(&table_string, ","); + } + + dynstr_append(&table_string, ")"); + ptr= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->string = (char *)my_malloc(PSI_NOT_INSTRUMENTED, table_string.length+1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->length= table_string.length+1; + ptr->type= CREATE_TABLE_TYPE; + strmov(ptr->string, table_string.str); + dynstr_free(&table_string); + DBUG_RETURN(ptr); +} + +/* + build_update_string() + + This function builds insert statements when the user opts to not supply + an insert file or string containing insert data +*/ +static statement * +build_update_string(void) +{ + char buf[HUGE_STRING_LENGTH]; + unsigned int col_count; + statement *ptr; + DYNAMIC_STRING update_string; + DBUG_ENTER("build_update_string"); + + init_dynamic_string(&update_string, "", 1024, 1024); + + dynstr_append(&update_string, "UPDATE t1 SET "); + + if (num_int_cols) + for (col_count= 1; col_count <= num_int_cols; col_count++) + { + if (snprintf(buf, HUGE_STRING_LENGTH, "intcol%d = %ld", col_count, + random()) > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating update\n"); + exit(1); + } + dynstr_append(&update_string, buf); + + if (col_count < num_int_cols || num_char_cols > 0) + dynstr_append_mem(&update_string, ",", 1); + } + + if (num_char_cols) + for (col_count= 1; col_count <= num_char_cols; col_count++) + { + char rand_buffer[RAND_STRING_SIZE]; + int buf_len= get_random_string(rand_buffer); + + if (snprintf(buf, HUGE_STRING_LENGTH, "charcol%d = '%.*s'", col_count, + buf_len, rand_buffer) + > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating update\n"); + exit(1); + } + dynstr_append(&update_string, buf); + + if (col_count < num_char_cols) + dynstr_append_mem(&update_string, ",", 1); + } + + if (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary) + dynstr_append(&update_string, " WHERE id = "); + + + ptr= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + + ptr->string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, update_string.length + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->length= update_string.length+1; + if (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary) + ptr->type= UPDATE_TYPE_REQUIRES_PREFIX ; + else + ptr->type= UPDATE_TYPE; + + strmov(ptr->string, update_string.str); + dynstr_free(&update_string); + DBUG_RETURN(ptr); +} + + +/* + build_insert_string() + + This function builds insert statements when the user opts to not supply + an insert file or string containing insert data +*/ +static statement * +build_insert_string(void) +{ + char buf[HUGE_STRING_LENGTH]; + unsigned int col_count; + statement *ptr; + DYNAMIC_STRING insert_string; + DBUG_ENTER("build_insert_string"); + + init_dynamic_string(&insert_string, "", 1024, 1024); + + dynstr_append(&insert_string, "INSERT INTO t1 VALUES ("); + + if (auto_generate_sql_autoincrement) + { + dynstr_append(&insert_string, "NULL"); + + if (num_int_cols || num_char_cols) + dynstr_append(&insert_string, ","); + } + + if (auto_generate_sql_guid_primary) + { + dynstr_append(&insert_string, "uuid()"); + + if (num_int_cols || num_char_cols) + dynstr_append(&insert_string, ","); + } + + if (auto_generate_sql_secondary_indexes) + { + unsigned int count; + + for (count= 0; count < auto_generate_sql_secondary_indexes; count++) + { + if (count) /* Except for the first pass we add a comma */ + dynstr_append(&insert_string, ","); + + dynstr_append(&insert_string, "uuid()"); + } + + if (num_int_cols || num_char_cols) + dynstr_append(&insert_string, ","); + } + + if (num_int_cols) + for (col_count= 1; col_count <= num_int_cols; col_count++) + { + if (snprintf(buf, HUGE_STRING_LENGTH, "%ld", random()) > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating insert\n"); + exit(1); + } + dynstr_append(&insert_string, buf); + + if (col_count < num_int_cols || num_char_cols > 0) + dynstr_append_mem(&insert_string, ",", 1); + } + + if (num_char_cols) + for (col_count= 1; col_count <= num_char_cols; col_count++) + { + int buf_len= get_random_string(buf); + dynstr_append_mem(&insert_string, "'", 1); + dynstr_append_mem(&insert_string, buf, buf_len); + dynstr_append_mem(&insert_string, "'", 1); + + if (col_count < num_char_cols) + dynstr_append_mem(&insert_string, ",", 1); + } + + dynstr_append_mem(&insert_string, ")", 1); + + ptr= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, insert_string.length + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->length= insert_string.length+1; + ptr->type= INSERT_TYPE; + strmov(ptr->string, insert_string.str); + dynstr_free(&insert_string); + DBUG_RETURN(ptr); +} + + +/* + build_select_string() + + This function builds a query if the user opts to not supply a query + statement or file containing a query statement +*/ +static statement * +build_select_string(my_bool key) +{ + char buf[HUGE_STRING_LENGTH]; + unsigned int col_count; + statement *ptr; + static DYNAMIC_STRING query_string; + DBUG_ENTER("build_select_string"); + + init_dynamic_string(&query_string, "", 1024, 1024); + + dynstr_append_mem(&query_string, "SELECT ", 7); + for (col_count= 1; col_count <= num_int_cols; col_count++) + { + if (snprintf(buf, HUGE_STRING_LENGTH, "intcol%d", col_count) + > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating select\n"); + exit(1); + } + dynstr_append(&query_string, buf); + + if (col_count < num_int_cols || num_char_cols > 0) + dynstr_append_mem(&query_string, ",", 1); + + } + for (col_count= 1; col_count <= num_char_cols; col_count++) + { + if (snprintf(buf, HUGE_STRING_LENGTH, "charcol%d", col_count) + > HUGE_STRING_LENGTH) + { + fprintf(stderr, "Memory Allocation error in creating select\n"); + exit(1); + } + dynstr_append(&query_string, buf); + + if (col_count < num_char_cols) + dynstr_append_mem(&query_string, ",", 1); + + } + dynstr_append(&query_string, " FROM t1"); + + if ((key) && + (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary)) + dynstr_append(&query_string, " WHERE id = "); + + ptr= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, query_string.length + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + ptr->length= query_string.length+1; + if ((key) && + (auto_generate_sql_autoincrement || auto_generate_sql_guid_primary)) + ptr->type= SELECT_TYPE_REQUIRES_PREFIX; + else + ptr->type= SELECT_TYPE; + + strmov(ptr->string, query_string.str); + dynstr_free(&query_string); + DBUG_RETURN(ptr); +} + +static int +get_options(int *argc,char ***argv) +{ + int ho_error; + char *tmp_string; + MY_STAT sbuf; /* Stat information for the data file */ + + DBUG_ENTER("get_options"); + if ((ho_error= handle_options(argc, argv, my_long_options, get_one_option))) + exit(ho_error); + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg= MY_CHECK_ERROR; + + /* + If something is created and --no-drop is not specified, we drop the + schema. + */ + if (!opt_no_drop && (create_string || auto_generate_sql)) + opt_preserve= FALSE; + + if (auto_generate_sql && (create_string || user_supplied_query)) + { + fprintf(stderr, + "%s: Can't use --auto-generate-sql when create and query strings are specified!\n", + my_progname); + exit(1); + } + + if (auto_generate_sql && auto_generate_sql_guid_primary && + auto_generate_sql_autoincrement) + { + fprintf(stderr, + "%s: Either auto-generate-sql-guid-primary or auto-generate-sql-add-autoincrement can be used!\n", + my_progname); + exit(1); + } + + /* + We are testing to make sure that if someone specified a key search + that we actually added a key! + */ + if (auto_generate_sql && auto_generate_sql_type[0] == 'k') + if ( auto_generate_sql_autoincrement == FALSE && + auto_generate_sql_guid_primary == FALSE) + { + fprintf(stderr, + "%s: Can't perform key test without a primary key!\n", + my_progname); + exit(1); + } + + if (auto_generate_sql && num_of_query && auto_actual_queries) + { + fprintf(stderr, + "%s: Either auto-generate-sql-execute-number or number-of-queries can be used!\n", + my_progname); + exit(1); + } + + parse_comma(concurrency_str ? concurrency_str : "1", &concurrency); + + if (opt_csv_str) + { + opt_silent= TRUE; + + if (opt_csv_str[0] == '-') + { + csv_file= my_fileno(stdout); + } + else + { + if ((csv_file= my_open(opt_csv_str, O_CREAT|O_WRONLY|O_APPEND, MYF(0))) + == -1) + { + fprintf(stderr,"%s: Could not open csv file: %sn\n", + my_progname, opt_csv_str); + exit(1); + } + } + } + + if (opt_only_print) + opt_silent= TRUE; + + if (num_int_cols_opt) + { + option_string *str; + if(parse_option(num_int_cols_opt, &str, ',') == -1) + { + fprintf(stderr, "Invalid value specified for the option " + "'number-int-cols'\n"); + option_cleanup(str); + return 1; + } + num_int_cols= atoi(str->string); + if (str->option) + num_int_cols_index= atoi(str->option); + + option_cleanup(str); + } + + if (num_char_cols_opt) + { + option_string *str; + if(parse_option(num_char_cols_opt, &str, ',') == -1) + { + fprintf(stderr, "Invalid value specified for the option " + "'number-char-cols'\n"); + option_cleanup(str); + return 1; + } + num_char_cols= atoi(str->string); + if (str->option) + num_char_cols_index= atoi(str->option); + else + num_char_cols_index= 0; + + option_cleanup(str); + } + + + if (auto_generate_sql) + { + unsigned long long x= 0; + statement *ptr_statement; + + if (verbose >= 2) + printf("Building Create Statements for Auto\n"); + + create_statements= build_table_string(); + /* + Pre-populate table + */ + for (ptr_statement= create_statements, x= 0; + x < auto_generate_sql_unique_write_number; + x++, ptr_statement= ptr_statement->next) + { + ptr_statement->next= build_insert_string(); + } + + if (verbose >= 2) + printf("Building Query Statements for Auto\n"); + + if (auto_generate_sql_type[0] == 'r') + { + if (verbose >= 2) + printf("Generating SELECT Statements for Auto\n"); + + query_statements= build_select_string(FALSE); + for (ptr_statement= query_statements, x= 0; + x < auto_generate_sql_unique_query_number; + x++, ptr_statement= ptr_statement->next) + { + ptr_statement->next= build_select_string(FALSE); + } + } + else if (auto_generate_sql_type[0] == 'k') + { + if (verbose >= 2) + printf("Generating SELECT for keys Statements for Auto\n"); + + query_statements= build_select_string(TRUE); + for (ptr_statement= query_statements, x= 0; + x < auto_generate_sql_unique_query_number; + x++, ptr_statement= ptr_statement->next) + { + ptr_statement->next= build_select_string(TRUE); + } + } + else if (auto_generate_sql_type[0] == 'w') + { + /* + We generate a number of strings in case the engine is + Archive (since strings which were identical one after another + would be too easily optimized). + */ + if (verbose >= 2) + printf("Generating INSERT Statements for Auto\n"); + query_statements= build_insert_string(); + for (ptr_statement= query_statements, x= 0; + x < auto_generate_sql_unique_query_number; + x++, ptr_statement= ptr_statement->next) + { + ptr_statement->next= build_insert_string(); + } + } + else if (auto_generate_sql_type[0] == 'u') + { + query_statements= build_update_string(); + for (ptr_statement= query_statements, x= 0; + x < auto_generate_sql_unique_query_number; + x++, ptr_statement= ptr_statement->next) + { + ptr_statement->next= build_update_string(); + } + } + else /* Mixed mode is default */ + { + int coin= 0; + + query_statements= build_insert_string(); + /* + This logic should be extended to do a more mixed load, + at the moment it results in "every other". + */ + for (ptr_statement= query_statements, x= 0; + x < auto_generate_sql_unique_query_number; + x++, ptr_statement= ptr_statement->next) + { + if (coin) + { + ptr_statement->next= build_insert_string(); + coin= 0; + } + else + { + ptr_statement->next= build_select_string(TRUE); + coin= 1; + } + } + } + } + else + { + if (create_string && my_stat(create_string, &sbuf, MYF(0))) + { + File data_file; + if (!MY_S_ISREG(sbuf.st_mode)) + { + fprintf(stderr,"%s: Create file was not a regular file\n", + my_progname); + exit(1); + } + if ((data_file= my_open(create_string, O_RDWR, MYF(0))) == -1) + { + fprintf(stderr,"%s: Could not open create file\n", my_progname); + exit(1); + } + tmp_string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, (size_t)sbuf.st_size + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + my_read(data_file, (uchar*) tmp_string, (size_t)sbuf.st_size, MYF(0)); + tmp_string[sbuf.st_size]= '\0'; + my_close(data_file,MYF(0)); + parse_delimiter(tmp_string, &create_statements, delimiter[0]); + my_free(tmp_string); + } + else if (create_string) + { + parse_delimiter(create_string, &create_statements, delimiter[0]); + } + + if (user_supplied_query && my_stat(user_supplied_query, &sbuf, MYF(0))) + { + File data_file; + if (!MY_S_ISREG(sbuf.st_mode)) + { + fprintf(stderr,"%s: User query supplied file was not a regular file\n", + my_progname); + exit(1); + } + if ((data_file= my_open(user_supplied_query, O_RDWR, MYF(0))) == -1) + { + fprintf(stderr,"%s: Could not open query supplied file\n", my_progname); + exit(1); + } + tmp_string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, (size_t)sbuf.st_size + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + my_read(data_file, (uchar*) tmp_string, (size_t)sbuf.st_size, MYF(0)); + tmp_string[sbuf.st_size]= '\0'; + my_close(data_file,MYF(0)); + if (user_supplied_query) + actual_queries= parse_delimiter(tmp_string, &query_statements, + delimiter[0]); + my_free(tmp_string); + } + else if (user_supplied_query) + { + actual_queries= parse_delimiter(user_supplied_query, &query_statements, + delimiter[0]); + } + } + + if (user_supplied_pre_statements && my_stat(user_supplied_pre_statements, &sbuf, MYF(0))) + { + File data_file; + if (!MY_S_ISREG(sbuf.st_mode)) + { + fprintf(stderr,"%s: User query supplied file was not a regular file\n", + my_progname); + exit(1); + } + if ((data_file= my_open(user_supplied_pre_statements, O_RDWR, MYF(0))) == -1) + { + fprintf(stderr,"%s: Could not open query supplied file\n", my_progname); + exit(1); + } + tmp_string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, (size_t)sbuf.st_size + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + my_read(data_file, (uchar*) tmp_string, (size_t)sbuf.st_size, MYF(0)); + tmp_string[sbuf.st_size]= '\0'; + my_close(data_file,MYF(0)); + if (user_supplied_pre_statements) + (void)parse_delimiter(tmp_string, &pre_statements, + delimiter[0]); + my_free(tmp_string); + } + else if (user_supplied_pre_statements) + { + (void)parse_delimiter(user_supplied_pre_statements, + &pre_statements, + delimiter[0]); + } + + if (user_supplied_post_statements && my_stat(user_supplied_post_statements, &sbuf, MYF(0))) + { + File data_file; + if (!MY_S_ISREG(sbuf.st_mode)) + { + fprintf(stderr,"%s: User query supplied file was not a regular file\n", + my_progname); + exit(1); + } + if ((data_file= my_open(user_supplied_post_statements, O_RDWR, MYF(0))) == -1) + { + fprintf(stderr,"%s: Could not open query supplied file\n", my_progname); + exit(1); + } + tmp_string= (char *)my_malloc(PSI_NOT_INSTRUMENTED, (size_t)sbuf.st_size + 1, + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + my_read(data_file, (uchar*) tmp_string, (size_t)sbuf.st_size, MYF(0)); + tmp_string[sbuf.st_size]= '\0'; + my_close(data_file,MYF(0)); + if (user_supplied_post_statements) + (void)parse_delimiter(tmp_string, &post_statements, + delimiter[0]); + my_free(tmp_string); + } + else if (user_supplied_post_statements) + { + (void)parse_delimiter(user_supplied_post_statements, &post_statements, + delimiter[0]); + } + + if (verbose >= 2) + printf("Parsing engines to use.\n"); + + if (default_engine) + { + if(parse_option(default_engine, &engine_options, ',') == -1) + { + fprintf(stderr, "Invalid value specified for the option 'engine'\n"); + return 1; + } + } + + if (tty_password) + opt_password= my_get_tty_password(NullS); + + DBUG_RETURN(0); +} + + +static int run_query(MYSQL *mysql, const char *query, size_t len) +{ + if (opt_only_print) + { + printf("%.*s;\n", (int)len, query); + return 0; + } + + if (verbose >= 3) + printf("%.*s;\n", (int)len, query); + + return mysql_real_query(mysql, query, (ulong)len); +} + + +static int +generate_primary_key_list(MYSQL *mysql, option_string *engine_stmt) +{ + MYSQL_RES *result; + MYSQL_ROW row; + unsigned long long counter; + DBUG_ENTER("generate_primary_key_list"); + + /* + Blackhole is a special case, this allows us to test the upper end + of the server during load runs. + */ + if (opt_only_print || (engine_stmt && + strstr(engine_stmt->string, "blackhole"))) + { + primary_keys_number_of= 1; + primary_keys= (char **)my_malloc(PSI_NOT_INSTRUMENTED, + (size_t)(sizeof(char *) * primary_keys_number_of), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + /* Yes, we strdup a const string to simplify the interface */ + primary_keys[0]= my_strdup(PSI_NOT_INSTRUMENTED, "796c4422-1d94-102a-9d6d-00e0812d", MYF(0)); + } + else + { + if (run_query(mysql, "SELECT id from t1", strlen("SELECT id from t1"))) + { + fprintf(stderr,"%s: Cannot select GUID primary keys. (%s)\n", my_progname, + mysql_error(mysql)); + exit(1); + } + + if (!(result= mysql_store_result(mysql))) + { + fprintf(stderr, "%s: Error when storing result: %d %s\n", + my_progname, mysql_errno(mysql), mysql_error(mysql)); + exit(1); + } + primary_keys_number_of= mysql_num_rows(result); + + /* So why check this? Blackhole :) */ + if (primary_keys_number_of) + { + /* + We create the structure and loop and create the items. + */ + primary_keys= (char **)my_malloc(PSI_NOT_INSTRUMENTED, + (size_t)(sizeof(char *) * primary_keys_number_of), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + row= mysql_fetch_row(result); + for (counter= 0; counter < primary_keys_number_of; + counter++, row= mysql_fetch_row(result)) + primary_keys[counter]= my_strdup(PSI_NOT_INSTRUMENTED, row[0], MYF(0)); + } + + mysql_free_result(result); + } + + DBUG_RETURN(0); +} + +static int +drop_primary_key_list(void) +{ + unsigned long long counter; + + if (primary_keys_number_of) + { + for (counter= 0; counter < primary_keys_number_of; counter++) + my_free(primary_keys[counter]); + + my_free(primary_keys); + } + + return 0; +} + +static int +create_schema(MYSQL *mysql, const char *db, statement *stmt, + option_string *engine_stmt) +{ + char query[HUGE_STRING_LENGTH]; + statement *ptr; + statement *after_create; + int len; + ulonglong count; + DBUG_ENTER("create_schema"); + + len= snprintf(query, HUGE_STRING_LENGTH, "CREATE SCHEMA `%s`", db); + + if (verbose >= 2) + printf("Loading Pre-data\n"); + + if (run_query(mysql, query, len)) + { + fprintf(stderr,"%s: Cannot create schema %s : %s\n", my_progname, db, + mysql_error(mysql)); + exit(1); + } + + if (opt_only_print) + { + printf("use %s;\n", db); + } + else + { + if (verbose >= 3) + printf("%s;\n", query); + + if (mysql_select_db(mysql, db)) + { + fprintf(stderr,"%s: Cannot select schema '%s': %s\n",my_progname, db, + mysql_error(mysql)); + exit(1); + } + } + + count= 0; + after_create= stmt; + +limit_not_met: + for (ptr= after_create; ptr && ptr->length; ptr= ptr->next, count++) + { + if (auto_generate_sql && ( auto_generate_sql_number == count)) + break; + + if (engine_stmt && engine_stmt->option && ptr->type == CREATE_TABLE_TYPE) + { + char buffer[HUGE_STRING_LENGTH]; + + snprintf(buffer, HUGE_STRING_LENGTH, "%s Engine = %s %s", + ptr->string, engine_stmt->string, engine_stmt->option); + if (run_query(mysql, buffer, strlen(buffer))) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)ptr->length, ptr->string, mysql_error(mysql)); + exit(1); + } + } + else if (engine_stmt && engine_stmt->string && ptr->type == CREATE_TABLE_TYPE) + { + char buffer[HUGE_STRING_LENGTH]; + + snprintf(buffer, HUGE_STRING_LENGTH, "%s Engine = %s", + ptr->string, engine_stmt->string); + if (run_query(mysql, buffer, strlen(buffer))) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)ptr->length, ptr->string, mysql_error(mysql)); + exit(1); + } + } + else + { + if (run_query(mysql, ptr->string, ptr->length)) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)ptr->length, ptr->string, mysql_error(mysql)); + exit(1); + } + } + } + + if (auto_generate_sql && (auto_generate_sql_number > count )) + { + /* Special case for auto create, we don't want to create tables twice */ + after_create= stmt->next; + goto limit_not_met; + } + + DBUG_RETURN(0); +} + +static int +drop_schema(MYSQL *mysql, const char *db) +{ + char query[HUGE_STRING_LENGTH]; + int len; + + DBUG_ENTER("drop_schema"); + len= snprintf(query, HUGE_STRING_LENGTH, "DROP SCHEMA IF EXISTS `%s`", db); + + if (run_query(mysql, query, len)) + { + fprintf(stderr,"%s: Cannot drop database '%s' ERROR : %s\n", + my_progname, db, mysql_error(mysql)); + exit(1); + } + + + + DBUG_RETURN(0); +} + +static int +run_statements(MYSQL *mysql, statement *stmt) +{ + statement *ptr; + MYSQL_RES *result; + DBUG_ENTER("run_statements"); + + for (ptr= stmt; ptr && ptr->length; ptr= ptr->next) + { + if (run_query(mysql, ptr->string, ptr->length)) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)ptr->length, ptr->string, mysql_error(mysql)); + exit(1); + } + if (mysql_field_count(mysql)) + { + result= mysql_store_result(mysql); + mysql_free_result(result); + } + } + + DBUG_RETURN(0); +} + +static int +run_scheduler(stats *sptr, statement *stmts, uint concur, ulonglong limit) +{ + uint x; + struct timeval start_time, end_time; + thread_context con; + int error; + pthread_t mainthread; /* Thread descriptor */ + pthread_attr_t attr; /* Thread attributes */ + DBUG_ENTER("run_scheduler"); + + con.stmt= stmts; + con.limit= limit; + + pthread_attr_init(&attr); + if ((error= pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))) + { + printf("Got error: %d from pthread_attr_setdetachstate\n", error); + exit(1); + } + + pthread_mutex_lock(&counter_mutex); + thread_counter= 0; + + pthread_mutex_lock(&sleeper_mutex); + master_wakeup= 1; + pthread_mutex_unlock(&sleeper_mutex); + for (x= 0; x < concur; x++) + { + /* now you create the thread */ + if (pthread_create(&mainthread, &attr, run_task, + (void *)&con) != 0) + { + fprintf(stderr,"%s: Could not create thread\n", + my_progname); + exit(0); + } + thread_counter++; + } + pthread_mutex_unlock(&counter_mutex); + pthread_attr_destroy(&attr); + + pthread_mutex_lock(&sleeper_mutex); + master_wakeup= 0; + pthread_cond_broadcast(&sleep_threshhold); + pthread_mutex_unlock(&sleeper_mutex); + + gettimeofday(&start_time, NULL); + + /* + We loop until we know that all children have cleaned up. + */ + pthread_mutex_lock(&counter_mutex); + while (thread_counter) + { + struct timespec abstime; + + set_timespec(abstime, 3); + pthread_cond_timedwait(&count_threshhold, &counter_mutex, &abstime); + } + pthread_mutex_unlock(&counter_mutex); + + gettimeofday(&end_time, NULL); + + + sptr->timing= timedif(end_time, start_time); + sptr->users= concur; + sptr->rows= limit; + + DBUG_RETURN(0); +} + + +pthread_handler_t run_task(void *p) +{ + ulonglong queries; + ulonglong detach_counter; + unsigned int commit_counter; + MYSQL *mysql; + MYSQL_RES *result; + statement *ptr; + thread_context *con= (thread_context *)p; + + DBUG_ENTER("run_task"); + DBUG_PRINT("info", ("task script \"%s\"", con->stmt ? con->stmt->string : "")); + + pthread_mutex_lock(&sleeper_mutex); + while (master_wakeup) + { + pthread_cond_wait(&sleep_threshhold, &sleeper_mutex); + } + pthread_mutex_unlock(&sleeper_mutex); + + if (mysql_thread_init()) + { + fprintf(stderr,"%s: mysql_thread_init() failed\n", my_progname); + exit(0); + } + + if (!(mysql= mysql_init(NULL))) + { + fprintf(stderr,"%s: mysql_init() failed\n", my_progname); + mysql_thread_end(); + exit(0); + } + + set_mysql_connect_options(mysql); + + DBUG_PRINT("info", ("trying to connect to host %s as user %s", host, user)); + + if (!opt_only_print) + { + if (slap_connect(mysql)) + goto end; + } + + DBUG_PRINT("info", ("connected.")); + if (verbose >= 3) + printf("connected!\n"); + queries= 0; + + commit_counter= 0; + if (commit_rate) + run_query(mysql, "SET AUTOCOMMIT=0", strlen("SET AUTOCOMMIT=0")); + +limit_not_met: + for (ptr= con->stmt, detach_counter= 0; + ptr && ptr->length; + ptr= ptr->next, detach_counter++) + { + if (!opt_only_print && detach_rate && !(detach_counter % detach_rate)) + { + mysql_close(mysql); + + if (!(mysql= mysql_init(NULL))) + { + fprintf(stderr,"%s: mysql_init() failed ERROR : %s\n", + my_progname, mysql_error(mysql)); + exit(0); + } + if (slap_connect(mysql)) + goto end; + } + + /* + We have to execute differently based on query type. This should become a function. + */ + if ((ptr->type == UPDATE_TYPE_REQUIRES_PREFIX) || + (ptr->type == SELECT_TYPE_REQUIRES_PREFIX)) + { + int length; + unsigned int key_val; + char *key; + char buffer[HUGE_STRING_LENGTH]; + + /* + This should only happen if some sort of new engine was + implemented that didn't properly handle UPDATEs. + + Just in case someone runs this under an experimental engine we don't + want a crash so the if() is placed here. + */ + DBUG_ASSERT(primary_keys_number_of); + if (primary_keys_number_of) + { + key_val= (unsigned int)(random() % primary_keys_number_of); + key= primary_keys[key_val]; + + DBUG_ASSERT(key); + + length= snprintf(buffer, HUGE_STRING_LENGTH, "%.*s '%s'", + (int)ptr->length, ptr->string, key); + + if (run_query(mysql, buffer, length)) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)length, buffer, mysql_error(mysql)); + exit(0); + } + } + } + else + { + if (run_query(mysql, ptr->string, ptr->length)) + { + fprintf(stderr,"%s: Cannot run query %.*s ERROR : %s\n", + my_progname, (uint)ptr->length, ptr->string, mysql_error(mysql)); + exit(0); + } + } + + do + { + if (mysql_field_count(mysql)) + { + if (!(result= mysql_store_result(mysql))) + fprintf(stderr, "%s: Error when storing result: %d %s\n", + my_progname, mysql_errno(mysql), mysql_error(mysql)); + else + { + while (mysql_fetch_row(result)) {} + mysql_free_result(result); + } + } + } while(mysql_next_result(mysql) == 0); + queries++; + + if (commit_rate && (++commit_counter == commit_rate)) + { + commit_counter= 0; + run_query(mysql, C_STRING_WITH_LEN("COMMIT")); + } + + if (con->limit && queries == con->limit) + goto end; + } + + if (con->limit && queries < con->limit) + goto limit_not_met; + +end: + if (commit_rate) + run_query(mysql, C_STRING_WITH_LEN("COMMIT")); + + mysql_close(mysql); + + mysql_thread_end(); + + pthread_mutex_lock(&counter_mutex); + thread_counter--; + pthread_cond_signal(&count_threshhold); + pthread_mutex_unlock(&counter_mutex); + + DBUG_RETURN(0); +} + +int +parse_option(const char *origin, option_string **stmt, char delm) +{ + char *retstr; + char *ptr= (char *)origin; + option_string **sptr= stmt; + option_string *tmp; + size_t length= strlen(origin); + uint count= 0; /* We know that there is always one */ + + for (tmp= *sptr= (option_string *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(option_string), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + (retstr= strchr(ptr, delm)); + tmp->next= (option_string *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(option_string), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)), + tmp= tmp->next) + { + /* + Initialize buffer, because otherwise an + --engine=: