diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/CMakeLists.txt | 103 | ||||
-rw-r--r-- | client/async_example.c | 216 | ||||
-rw-r--r-- | client/client_metadata.h | 57 | ||||
-rw-r--r-- | client/client_priv.h | 155 | ||||
-rw-r--r-- | client/completion_hash.cc | 226 | ||||
-rw-r--r-- | client/completion_hash.h | 61 | ||||
-rw-r--r-- | client/echo.c | 45 | ||||
-rw-r--r-- | client/mariadb-conv.cc | 484 | ||||
-rw-r--r-- | client/my_readline.h | 42 | ||||
-rw-r--r-- | client/mysql.cc | 5610 | ||||
-rw-r--r-- | client/mysql_plugin.c | 1244 | ||||
-rw-r--r-- | client/mysql_upgrade.c | 1524 | ||||
-rw-r--r-- | client/mysqladmin.cc | 1712 | ||||
-rw-r--r-- | client/mysqlbinlog.cc | 3802 | ||||
-rw-r--r-- | client/mysqlcheck.c | 1298 | ||||
-rw-r--r-- | client/mysqldump.c | 7292 | ||||
-rw-r--r-- | client/mysqlimport.c | 788 | ||||
-rw-r--r-- | client/mysqlshow.c | 964 | ||||
-rw-r--r-- | client/mysqlslap.c | 2331 | ||||
-rw-r--r-- | client/mysqltest.cc | 12109 | ||||
-rw-r--r-- | client/readline.cc | 262 |
21 files changed, 40325 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>. +*/ + + +#ifndef _WIN32 +#include <poll.h> +#else +#include <WinSock2.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <mysql.h> + +#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 <host> <user> <password>\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 <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include <mysql.h> +#include <errmsg.h> +#include <my_getopt.h> +#include <mysql_version.h> + +#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 <andi@zend.com> + * and Zeev Suraski <zeev@zend.com> + * Small portability changes by Monty. Changed also to use my_malloc/my_free + */ + +#include <my_global.h> +#include <m_string.h> +#include <my_sys.h> +#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->count<count) { + *res_length=lm; + return return_b; + } + return_b=b; + lm++; + } + *res_length=lm; + return return_b; +} + + +void completion_hash_clean(HashTable *ht) +{ + free_root(&ht->mem_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 <sys/types.h> +#include <my_sys.h> + +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 <stdio.h> + +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 <andi@zend.com> + * Zeev Suraski <zeev@zend.com> + * Jani Tolonen <jani@mysql.com> + * Matt Wagner <matt@mysql.com> + * Jeremy Cole <jcole@mysql.com> + * Tonu Samuel <tonu@mysql.com> + * Harrison Fisk <harrison@mysql.com> + * + **/ + +#include "client_priv.h" +#include <m_ctype.h> +#include <stdarg.h> +#include <my_dir.h> +#ifndef __GNU_LIBRARY__ +#define __GNU_LIBRARY__ // Skip warnings in getopt.h +#endif +#include "my_readline.h" +#include <signal.h> +#include <violite.h> +#include <my_sys.h> +#include <source_revision.h> +#if defined(HAVE_LOCALE_H) +#include <locale.h> +#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 <curses.h> +#include <term.h> +#else +#if defined(HAVE_TERMIOS_H) +#include <termios.h> +#include <unistd.h> +#elif defined(HAVE_TERMBITS_H) +#include <termbits.h> +#elif defined(HAVE_ASM_TERMBITS_H) && (!defined __GLIBC__ || !(__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ > 0)) +#include <asm/termbits.h> // Standard linux +#endif +#undef VOID +#if defined(HAVE_TERMCAP_H) +#include <termcap.h> +#else +#ifdef HAVE_CURSES_H +#include <curses.h> +#endif +#undef SYSV // hack to avoid syntax error +#ifdef HAVE_TERM_H +#include <term.h> +#endif +#endif +#endif /* defined(HAVE_CURSES_H) && defined(HAVE_TERM_H) */ + +#undef bcmp // Fix problem with new readline +#if !defined(_WIN32) +# ifdef __APPLE__ +# include <editline/readline.h> +# else +# include <readline.h> +# if !defined(USE_LIBEDIT_INTERFACE) +# include <history.h> +# 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 <welcome_copyright_notice.h> // 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 <sslopt-case.h> + 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 + "<command>;DELIMITER <non-eof>") : similar to how a new line is + not added in the case when the DELIMITER is the first command + entered with an empty glob buffer. However, if the delimiter is + part of a string or a comment, the new line should be added. (e.g. + SELECT '\ndelimiter\n';\n) + */ + *out++='\n'; + length++; + } + if (buffer.length() + length >= buffer.alloced_length()) + buffer.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++ ; i<ht.nTableSize; i++) + { + if (ht.arBuckets[i]) + { + b = ht.arBuckets[i]; + e = b->pData; + break; + } + } + } + else + e = b->pData; + } + } + if (ptr) + return ptr; + } + return NullS; +} + + +/* Build up the completion hash */ + +static void build_completion_hash(bool rehash, bool write_info) +{ + COMMANDS *cmd=commands; + MYSQL_RES *databases=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 <item>',\nwhere <item> is one of the following", INFO_INFO); + num_name= 0; + num_cat= 1; + } + else if ((cur= mysql_fetch_row(result))) + { + tee_fprintf(PAGER, "You asked for help about help category: \"%s\"\n", cur[0]); + put_info("For more information, type 'help <item>', where <item> is one of the following", INFO_INFO); + num_name= 1; + num_cat= 2; + print_help_item(&cur,1,2,&last_char); + } + + while ((cur= mysql_fetch_row(result))) + print_help_item(&cur,num_name,num_cat,&last_char); + tee_fprintf(PAGER, "\n"); + end_pager(); + } + else + { + put_info("\nNothing found", INFO_INFO); + if (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("<TABLE BORDER=1>", PAGER); + if (column_names) + { + (void) tee_fputs("<TR>", PAGER); + while((field = mysql_fetch_field(result))) + { + tee_fputs("<TH>", PAGER); + if (field->name && field->name[0]) + xmlencode_print(field->name, field->name_length); + else + tee_fputs(field->name ? " " : "NULL", PAGER); + tee_fputs("</TH>", PAGER); + } + (void) tee_fputs("</TR>", PAGER); + } + while ((cur = mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths=mysql_fetch_lengths(result); + field= mysql_fetch_fields(result); + (void) tee_fputs("<TR>", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + (void) tee_fputs("<TD>", PAGER); + if (opt_binhex && is_binary_field(&field[i])) + print_as_hex(PAGER, cur[i], lengths[i], lengths[i]); + else + xmlencode_print(cur[i], lengths[i]); + (void) tee_fputs("</TD>", PAGER); + } + (void) tee_fputs("</TR>", PAGER); + } + (void) tee_fputs("</TABLE>", PAGER); +} + + +static void +print_table_data_xml(MYSQL_RES *result) +{ + MYSQL_ROW cur; + MYSQL_FIELD *fields; + + mysql_field_seek(result,0); + + tee_fputs("<?xml version=\"1.0\"?>\n\n<resultset statement=\"", PAGER); + xmlencode_print(glob_buffer.ptr(), (int)strlen(glob_buffer.ptr())); + tee_fputs("\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">", + PAGER); + + fields = mysql_fetch_fields(result); + while ((cur = mysql_fetch_row(result))) + { + if (interrupted_query) + break; + ulong *lengths=mysql_fetch_lengths(result); + (void) tee_fputs("\n <row>\n", PAGER); + for (uint i=0; i < mysql_num_fields(result); i++) + { + tee_fprintf(PAGER, "\t<field name=\""); + xmlencode_print(fields[i].name, (uint) strlen(fields[i].name)); + if (cur[i]) + { + tee_fprintf(PAGER, "\">"); + if (opt_binhex && is_binary_field(&fields[i])) + print_as_hex(PAGER, cur[i], lengths[i], lengths[i]); + else + xmlencode_print(cur[i], lengths[i]); + tee_fprintf(PAGER, "</field>\n"); + } + else + tee_fprintf(PAGER, "\" xsi:nil=\"true\" />\n"); + } + (void) tee_fputs(" </row>\n", PAGER); + } + (void) tee_fputs("</resultset>\n", PAGER); +} + + +static void +print_table_data_vertically(MYSQL_RES *result) +{ + MYSQL_ROW cur; + uint max_length=0; + MYSQL_FIELD *field; + + while ((field = mysql_fetch_field(result))) + { + 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: \\. <filename> | source <filename>", + 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 <my_global.h> +#include <m_string.h> +#include <mysql.h> +#include <my_getopt.h> +#include <my_dir.h> +#include <mysql_version.h> + +#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 <plugin-dir>/<plugin_name>.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] <plugin> 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 " + "'<plugin> ENABLE' or '<plugin> 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 <plugin_name>.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 + <plugin_name>.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 <sslopt-vars.h> +#include <../scripts/mysql_fix_privilege_tables_sql.c> + +#include <welcome_copyright_notice.h> /* ORACLE_WELCOME_COPYRIGHT_NOTICE */ + +#define VER "2.1" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#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 <sslopt-longopts.h> + {"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<datadir>\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 <signal.h> +#include <my_pthread.h> /* because of signal() */ +#include <sys/stat.h> +#include <mysql.h> +#include <mysql_version.h> +#include <welcome_copyright_notice.h> +#include <my_rnd.h> +#include <password.h> +#include <my_sys.h> + +#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 <sslopt-vars.h> + +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 <sslopt-longopts.h> +#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 <sslopt-case.h> + 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 <my_time.h> +#include <sslopt-vars.h> +/* That one is necessary for defines of OPTION_NO_FOREIGN_KEY_CHECKS etc */ +#include "sql_priv.h" +#include "sql_basic_types.h" +#include <atomic> +#include "log_event.h" +#include "compat56.h" +#include "sql_common.h" +#include "my_dir.h" +#include <welcome_copyright_notice.h> // 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 <algorithm> + +#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 <db>" 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 <sslopt-longopts.h> + {"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<char*>(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<char *>(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 <typename T> +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 <sslopt-case.h> + 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<Domain_gtid_event_filter>( + "--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<Domain_gtid_event_filter>( + "--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<Server_gtid_event_filter>( + "--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<Server_gtid_event_filter>( + "--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_gtid_event_filter>( + "--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<uchar*>(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 <m_ctype.h> +#include <mysql_version.h> +#include <mysqld_error.h> +#include <sslopt-vars.h> +#include <welcome_copyright_notice.h> /* 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 <sslopt-longopts.h> + {"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 <sslopt-case.h> + 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 <david@ols.es> +** master/autocommit code by Brian Aker <brian@tangent.org> +** SSL by +** Andrei Errapart <andreie@no.spam.ee> +** Tõnu Samuel <tonu@please.do.not.remove.this.spam.ee> +** XML by Gary Huntress <ghuntress@mediaone.net> 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 <my_global.h> +#include <my_sys.h> +#include <my_user.h> +#include <m_string.h> +#include <m_ctype.h> +#include <hash.h> +#include <stdarg.h> + +#include "client_priv.h" +#include "mysql.h" +#include "mysql_version.h" +#include "mysqld_error.h" + +#include <welcome_copyright_notice.h> /* 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 <sslopt-vars.h> +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=<host>, MASTER_PORT=<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 <sslopt-longopts.h> + {"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("<?xml version=\"1.0\"?>\n", sql_file); + /* + Schema reference. Allows use of xsi:nil for NULL values and + xsi:type to define an element's data type. + */ + fputs("<mysqldump ", sql_file); + fputs("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", + sql_file); + 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("</mysqldump>\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 <sslopt-case.h> + 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=<database>.<table>\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=<database>.<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 <tabname>" + + 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: + sbeg<tag_name first_attribute_name="first_attribute_value" ... + attribute_name_n="attribute_value_n">send + 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: + <stag_atr="sval" xsi:nil="true"/> + 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, <![CDATA[]]]]> and <![CDATA[>]]. +*/ + +static void print_xml_cdata(FILE *xml_file, const char *str, ulong len) +{ + const char *end; + + fputs("<![CDATA[\n", xml_file); + for (end= str + len; str != end; str++) + { + switch(*str) { + case ']': + if ((*(str + 1) == ']') && (*(str + 2) =='>')) + { + fputs("]]]]><![CDATA[>", 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<row_name Atr1="Val1" Atr2="Val2"... /> + 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</%s>\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 comment string \n -->\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("<!-- ", xml_file); + + for (end= comment_string + len; comment_string != end; comment_string++) + { + /* + The string "--" (double-hyphen) MUST NOT occur within xml comments. + */ + switch (*comment_string) { + case '-': + if (*(comment_string + 1) == '-') /* Only one hyphen allowed. */ + break; + /* fall through */ + default: + fputc(*comment_string, xml_file); + break; + } + } + 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<events>\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</events>\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<routines>\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</routines>\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<options Comment=\"view\" />\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</table_structure>\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</triggers>\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<row>\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("</field>\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("</field>\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</row>\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</table_data>\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<tables ; i++) + { + mysql_real_escape_string(mysql, name_buff, + table_names[i], (ulong)strlen(table_names[i])); + + dynstr_append_checked(&dynamic_where, "'"); + dynstr_append_checked(&dynamic_where, name_buff); + dynstr_append_checked(&dynamic_where, "',"); + } + dynstr_trunc(&dynamic_where, 1); + dynstr_append_checked(&dynamic_where,"))"); + + DBUG_PRINT("info",("Dump TS for Tables where: %s",dynamic_where.str)); + r= dump_tablespaces(dynamic_where.str); + dynstr_free(&dynamic_where); + return r; +} + + +static int dump_tablespaces_for_databases(char** databases) +{ + int r; + int i; + + init_dynamic_string_checked(&dynamic_where, " AND TABLESPACE_NAME IN (" + "SELECT DISTINCT TABLESPACE_NAME FROM" + " INFORMATION_SCHEMA.PARTITIONS" + " WHERE" + " TABLE_SCHEMA IN (", 256, 1024); + + for (i=0 ; databases[i]!=NULL ; i++) + { + char db_name_buff[NAME_LEN*2+3]; + mysql_real_escape_string(mysql, db_name_buff, + databases[i], (ulong)strlen(databases[i])); + dynstr_append_checked(&dynamic_where, "'"); + dynstr_append_checked(&dynamic_where, db_name_buff); + dynstr_append_checked(&dynamic_where, "',"); + } + dynstr_trunc(&dynamic_where, 1); + dynstr_append_checked(&dynamic_where,"))"); + + DBUG_PRINT("info",("Dump TS for DBs where: %s",dynamic_where.str)); + r= dump_tablespaces(dynamic_where.str); + dynstr_free(&dynamic_where); + return r; +} + + +static int dump_tablespaces(char* ts_where) +{ + MYSQL_ROW row; + MYSQL_RES *tableres; + char buf[FN_REFLEN]; + DYNAMIC_STRING sqlbuf; + int first= 0; + /* + The following are used for parsing the EXTRA field + */ + char extra_format[]= "UNDO_BUFFER_SIZE="; + char *ubs; + char *endsemi; + DBUG_ENTER("dump_tablespaces"); + + /* + Try to turn off semi-join optimization (if that fails, this is a + pre-optimizer_switch server, and the old query plan is ok for us. + */ + mysql_query(mysql, "set optimizer_switch='semijoin=off'"); + + init_dynamic_string_checked(&sqlbuf, + "SELECT LOGFILE_GROUP_NAME," + " FILE_NAME," + " TOTAL_EXTENTS," + " INITIAL_SIZE," + " ENGINE," + " EXTRA" + " FROM INFORMATION_SCHEMA.FILES" + " WHERE FILE_TYPE = 'UNDO LOG'" + " AND FILE_NAME IS NOT NULL" + " AND LOGFILE_GROUP_NAME IS NOT NULL", + 256, 1024); + if(ts_where) + { + dynstr_append_checked(&sqlbuf, + " AND LOGFILE_GROUP_NAME IN (" + "SELECT DISTINCT LOGFILE_GROUP_NAME" + " FROM INFORMATION_SCHEMA.FILES" + " WHERE FILE_TYPE = 'DATAFILE'" + ); + dynstr_append_checked(&sqlbuf, ts_where); + dynstr_append_checked(&sqlbuf, ")"); + } + dynstr_append_checked(&sqlbuf, + " GROUP BY LOGFILE_GROUP_NAME, FILE_NAME" + ", ENGINE, TOTAL_EXTENTS, INITIAL_SIZE" + " ORDER BY LOGFILE_GROUP_NAME"); + + if (mysql_query(mysql, sqlbuf.str) || + !(tableres = mysql_store_result(mysql))) + { + dynstr_free(&sqlbuf); + if (mysql_errno(mysql) == ER_BAD_TABLE_ERROR || + mysql_errno(mysql) == ER_BAD_DB_ERROR || + mysql_errno(mysql) == ER_UNKNOWN_TABLE) + { + fprintf(md_result_file, + "\n--\n-- Not dumping tablespaces as no INFORMATION_SCHEMA.FILES" + " table on this server\n--\n"); + check_io(md_result_file); + DBUG_RETURN(0); + } + + fprintf(stderr, "%s: Error: '%s' when trying to dump tablespaces\n", + my_progname_short, mysql_error(mysql)); + DBUG_RETURN(1); + } + + buf[0]= 0; + while ((row= mysql_fetch_row(tableres))) + { + if (strcmp(buf, row[0]) != 0) + first= 1; + if (first) + { + print_comment(md_result_file, 0, "\n--\n-- Logfile group: %s\n--\n", + fix_for_comment(row[0])); + + fprintf(md_result_file, "\nCREATE"); + } + else + { + fprintf(md_result_file, "\nALTER"); + } + fprintf(md_result_file, + " LOGFILE GROUP %s\n" + " ADD UNDOFILE '%s'\n", + row[0], + row[1]); + if (first) + { + ubs= strstr(row[5],extra_format); + if(!ubs) + break; + ubs+= strlen(extra_format); + endsemi= strstr(ubs,";"); + if(endsemi) + endsemi[0]= '\0'; + fprintf(md_result_file, + " UNDO_BUFFER_SIZE %s\n", + ubs); + } + fprintf(md_result_file, + " INITIAL_SIZE %s\n" + " ENGINE=%s;\n", + row[3], + row[4]); + check_io(md_result_file); + if (first) + { + first= 0; + strxmov(buf, row[0], NullS); + } + } + dynstr_free(&sqlbuf); + mysql_free_result(tableres); + init_dynamic_string_checked(&sqlbuf, + "SELECT DISTINCT TABLESPACE_NAME," + " FILE_NAME," + " LOGFILE_GROUP_NAME," + " EXTENT_SIZE," + " INITIAL_SIZE," + " ENGINE" + " FROM INFORMATION_SCHEMA.FILES" + " WHERE FILE_TYPE = 'DATAFILE'", + 256, 1024); + + if(ts_where) + dynstr_append_checked(&sqlbuf, ts_where); + + dynstr_append_checked(&sqlbuf, " ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME"); + + if (mysql_query_with_error_report(mysql, &tableres, sqlbuf.str)) + { + dynstr_free(&sqlbuf); + DBUG_RETURN(1); + } + + buf[0]= 0; + while ((row= mysql_fetch_row(tableres))) + { + if (strcmp(buf, row[0]) != 0) + first= 1; + if (first) + { + print_comment(md_result_file, 0, "\n--\n-- Tablespace: %s\n--\n", + fix_for_comment(row[0])); + fprintf(md_result_file, "\nCREATE"); + } + else + { + fprintf(md_result_file, "\nALTER"); + } + fprintf(md_result_file, + " TABLESPACE %s\n" + " ADD DATAFILE '%s'\n", + row[0], + row[1]); + if (first) + { + fprintf(md_result_file, + " USE LOGFILE GROUP %s\n" + " EXTENT_SIZE %s\n", + row[2], + row[3]); + } + fprintf(md_result_file, + " INITIAL_SIZE %s\n" + " ENGINE=%s;\n", + row[4], + row[5]); + check_io(md_result_file); + if (first) + { + first= 0; + strxmov(buf, row[0], NullS); + } + } + + mysql_free_result(tableres); + dynstr_free(&sqlbuf); + mysql_query(mysql, "set optimizer_switch=default"); + + DBUG_RETURN(0); +} + + +/* Return 1 if we should copy the database */ +static my_bool include_database(const char *hash_key) +{ + return !my_hash_search(&ignore_database, (uchar*) hash_key, strlen(hash_key)); +} + + +static int dump_all_databases() +{ + MYSQL_ROW row; + MYSQL_RES *tableres; + int result=0; + + if (mysql_query_with_error_report(mysql, &tableres, "SHOW DATABASES")) + 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_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("</database>\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("</database>\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("</database>\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 <my_sys.h> + +#include "mysql_version.h" + +#include <welcome_copyright_notice.h> /* 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 <sslopt-vars.h> + +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 <sslopt-longopts.h> + {"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 <sslopt-case.h> + 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 <my_sys.h> +#include <m_string.h> +#include <mysql.h> +#include <mysqld_error.h> +#include <signal.h> +#include <stdarg.h> +#include <sslopt-vars.h> +#include <welcome_copyright_notice.h> /* 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 <sslopt-longopts.h> +#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 <sslopt-case.h> + 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 <mysqld_error.h> +#include <my_dir.h> +#include <signal.h> +#include <sslopt-vars.h> +#ifndef _WIN32 +#include <sys/wait.h> +#endif +#include <ctype.h> +#include <welcome_copyright_notice.h> /* 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 <sslopt-longopts.h> +#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 <sslopt-case.h> + 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=<storage_engine>:<option>,<eng1>,<eng2> + will crash. + */ + char buffer[HUGE_STRING_LENGTH]= ""; + char *buffer_ptr; + + /* + Return an error if the length of the comma separated values + exceeds HUGE_STRING_LENGTH. + */ + if ((size_t)(retstr - ptr) > HUGE_STRING_LENGTH) + return -1; + + count++; + strncpy(buffer, ptr, (size_t)(retstr - ptr)); + /* + Handle --engine=memory:max_row=200 cases, or more general speaking + --engine=<storage_engine>:<options>, which will be translated to + Engine = storage_engine option. + */ + if ((buffer_ptr= strchr(buffer, ':'))) + { + char *option_ptr; + + tmp->length= (size_t)(buffer_ptr - buffer); + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, (uint)tmp->length, MYF(MY_FAE)); + + option_ptr= ptr + 1 + tmp->length; + + /* Move past the : and the first string */ + tmp->option_length= (size_t)(retstr - option_ptr); + tmp->option= my_strndup(PSI_NOT_INSTRUMENTED, option_ptr, (uint)tmp->option_length, + MYF(MY_FAE)); + } + else + { + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, (size_t)(retstr - ptr), MYF(MY_FAE)); + tmp->length= (size_t)(retstr - ptr); + } + + /* Skip delimiter delm */ + ptr+= retstr - ptr + 1; + if (isspace(*ptr)) + ptr++; + + count++; + } + + if (ptr != origin + length) + { + char *origin_ptr; + + /* + Return an error if the length of any of the comma separated values + exceeds HUGE_STRING_LENGTH. + */ + if (strlen(ptr) > HUGE_STRING_LENGTH) + return -1; + + if ((origin_ptr= strchr(ptr, ':'))) + { + char *option_ptr; + + tmp->length= (size_t)(origin_ptr - ptr); + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, tmp->length, MYF(MY_FAE)); + + option_ptr= (char *)ptr + 1 + tmp->length; + + /* Move past the : and the first string */ + tmp->option_length= strlen(option_ptr); + tmp->option= my_strndup(PSI_NOT_INSTRUMENTED, option_ptr, tmp->option_length, + MYF(MY_FAE)); + } + else + { + tmp->length= strlen(ptr); + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, tmp->length, MYF(MY_FAE)); + } + + count++; + } + + return count; +} + + +uint +parse_delimiter(const char *script, statement **stmt, char delm) +{ + char *retstr; + char *ptr= (char *)script; + statement **sptr= stmt; + statement *tmp; + size_t length= strlen(script); + uint count= 0; /* We know that there is always one */ + + for (tmp= *sptr= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + (retstr= strchr(ptr, delm)); + tmp->next= (statement *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(statement), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)), + tmp= tmp->next) + { + count++; + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, (uint)(retstr - ptr), MYF(MY_FAE)); + tmp->length= (size_t)(retstr - ptr); + ptr+= retstr - ptr + 1; + if (isspace(*ptr)) + ptr++; + } + + if (ptr != script+length) + { + tmp->string= my_strndup(PSI_NOT_INSTRUMENTED, ptr, (uint)((script + length) - ptr), + MYF(MY_FAE)); + tmp->length= (size_t)((script + length) - ptr); + count++; + } + + return count; +} + + +uint +parse_comma(const char *string, uint **range) +{ + uint count= 1,x; /* We know that there is always one */ + char *retstr; + char *ptr= (char *)string; + uint *nptr; + + for (;*ptr; ptr++) + if (*ptr == ',') count++; + + /* One extra spot for the NULL */ + nptr= *range= (uint *)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(uint) * (count + 1), + MYF(MY_ZEROFILL|MY_FAE|MY_WME)); + + ptr= (char *)string; + x= 0; + while ((retstr= strchr(ptr,','))) + { + nptr[x++]= atoi(ptr); + ptr+= retstr - ptr + 1; + } + nptr[x++]= atoi(ptr); + + return count; +} + +void +print_conclusions(conclusions *con) +{ + printf("Benchmark\n"); + if (con->engine) + printf("\tRunning for engine %s\n", con->engine); + printf("\tAverage number of seconds to run all queries: %ld.%03ld seconds\n", + con->avg_timing / 1000, con->avg_timing % 1000); + printf("\tMinimum number of seconds to run all queries: %ld.%03ld seconds\n", + con->min_timing / 1000, con->min_timing % 1000); + printf("\tMaximum number of seconds to run all queries: %ld.%03ld seconds\n", + con->max_timing / 1000, con->max_timing % 1000); + printf("\tNumber of clients running queries: %d\n", con->users); + printf("\tAverage number of queries per client: %llu\n", con->avg_rows); + printf("\n"); +} + +void +print_conclusions_csv(conclusions *con) +{ + char buffer[HUGE_STRING_LENGTH]; + const char *ptr= auto_generate_sql_type ? auto_generate_sql_type : "query"; + + snprintf(buffer, HUGE_STRING_LENGTH, + "%s,%s,%ld.%03ld,%ld.%03ld,%ld.%03ld,%d,%llu\n", + con->engine ? con->engine : "", /* Storage engine we ran against */ + ptr, /* Load type */ + con->avg_timing / 1000, con->avg_timing % 1000, /* Time to load */ + con->min_timing / 1000, con->min_timing % 1000, /* Min time */ + con->max_timing / 1000, con->max_timing % 1000, /* Max time */ + con->users, /* Children used */ + con->avg_rows /* Queries run */ + ); + my_write(csv_file, (uchar*) buffer, (uint)strlen(buffer), MYF(0)); +} + +void +generate_stats(conclusions *con, option_string *eng, stats *sptr) +{ + stats *ptr; + unsigned int x; + + con->min_timing= sptr->timing; + con->max_timing= sptr->timing; + con->min_rows= sptr->rows; + con->max_rows= sptr->rows; + + /* At the moment we assume uniform */ + con->users= sptr->users; + con->avg_rows= sptr->rows; + + /* With no next, we know it is the last element that was malloced */ + for (ptr= sptr, x= 0; x < iterations; ptr++, x++) + { + con->avg_timing+= ptr->timing; + + if (ptr->timing > con->max_timing) + con->max_timing= ptr->timing; + if (ptr->timing < con->min_timing) + con->min_timing= ptr->timing; + } + con->avg_timing= con->avg_timing/iterations; + + if (eng && eng->string) + con->engine= eng->string; + else + con->engine= NULL; +} + +void +option_cleanup(option_string *stmt) +{ + option_string *ptr, *nptr; + if (!stmt) + return; + + for (ptr= stmt; ptr; ptr= nptr) + { + nptr= ptr->next; + my_free(ptr->string); + my_free(ptr->option); + my_free(ptr); + } +} + +void +statement_cleanup(statement *stmt) +{ + statement *ptr, *nptr; + if (!stmt) + return; + + for (ptr= stmt; ptr; ptr= nptr) + { + nptr= ptr->next; + my_free(ptr->string); + my_free(ptr); + } +} + + +int +slap_connect(MYSQL *mysql) +{ + /* Connect to server */ + static ulong connection_retry_sleep= 100000; /* Microseconds */ + int x, connect_error= 1; + for (x= 0; x < 10; x++) + { + set_mysql_connect_options(mysql); + if (opt_init_command) + mysql_options(mysql, MYSQL_INIT_COMMAND, opt_init_command); + if (mysql_real_connect(mysql, host, user, opt_password, + create_schema_string, + opt_mysql_port, + opt_mysql_unix_port, + connect_flags)) + { + /* Connect succeeded */ + connect_error= 0; + break; + } + my_sleep(connection_retry_sleep); + } + if (connect_error) + { + fprintf(stderr,"%s: Error when connecting to server: %d %s\n", + my_progname, mysql_errno(mysql), mysql_error(mysql)); + return 1; + } + + return 0; +} diff --git a/client/mysqltest.cc b/client/mysqltest.cc new file mode 100644 index 00000000..87c5a62a --- /dev/null +++ b/client/mysqltest.cc @@ -0,0 +1,12109 @@ +/* Copyright (c) 2000, 2013, 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 */ + +/* + mysqltest + + Tool used for executing a .test file + + See the "MySQL Test framework manual" for more information + https://mariadb.com/kb/en/library/mysqltest/ + + Please keep the test framework tools identical in all versions! + + Written by: + Sasha Pachev <sasha@mysql.com> + Matt Wagner <matt@mysql.com> + Monty + Jani + Holyfoot + And many others +*/ + +#define MTEST_VERSION "3.5" + +#include "client_priv.h" +#include <mysql_version.h> +#include <mysqld_error.h> +#include <sql_common.h> +#include <m_ctype.h> +#include "client_metadata.h" +#include <my_dir.h> +#include <hash.h> +#include <stdarg.h> +#include <violite.h> +#define PCRE2_STATIC 1 /* Important on Windows */ +#include "pcre2posix.h" /* pcreposix regex library */ +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifdef _WIN32 +#include <direct.h> +#endif +#include <signal.h> +#include <my_stacktrace.h> + +#include <welcome_copyright_notice.h> // ORACLE_WELCOME_COPYRIGHT_NOTICE + +#ifdef _WIN32 +#include <crtdbg.h> +#define SIGNAL_FMT "exception 0x%x" +#else +#define SIGNAL_FMT "signal %d" +#endif + +static my_bool non_blocking_api_enabled= 0; +#if !defined(EMBEDDED_LIBRARY) +#define WRAP_NONBLOCK_ENABLED non_blocking_api_enabled +#include "../tests/nonblock-wrappers.h" +#endif + + +#define MAX_VAR_NAME_LENGTH 256 +#define MAX_COLUMNS 256 +#define MAX_EMBEDDED_SERVER_ARGS 64 +#define MAX_DELIMITER_LENGTH 16 +#define DEFAULT_MAX_CONN 64 + +#define DIE_BUFF_SIZE 256*1024 + +/* Flags controlling send and reap */ +#define QUERY_SEND_FLAG 1 +#define QUERY_REAP_FLAG 2 + +#define QUERY_PRINT_ORIGINAL_FLAG 4 + +#define CLOSED_CONNECTION "-closed_connection-" + +#ifndef HAVE_SETENV +static int setenv(const char *name, const char *value, int overwrite); +#endif + +C_MODE_START +static sig_handler signal_handler(int sig); +static my_bool get_one_option(const struct my_option *, const char *, + const char *); +C_MODE_END + +enum { + OPT_LOG_DIR=OPT_MAX_CLIENT_OPTION, OPT_RESULT_FORMAT_VERSION +}; + +static int record= 0, opt_sleep= -1; +static char *opt_db= 0, *opt_pass= 0; +const char *opt_user= 0, *opt_host= 0, *unix_sock= 0, *opt_basedir= "./"; +const char *opt_logdir= ""; +const char *opt_prologue= 0, *opt_charsets_dir; +static int opt_port= 0; +static int opt_max_connect_retries; +static int opt_result_format_version; +static int opt_max_connections= DEFAULT_MAX_CONN; +static int error_count= 0; +static my_bool opt_compress= 0, silent= 0, verbose= 0; +static my_bool debug_info_flag= 0, debug_check_flag= 0; +static my_bool tty_password= 0; +static my_bool opt_mark_progress= 0; +static my_bool ps_protocol= 0, ps_protocol_enabled= 0, ps2_protocol_enabled= 0; +static my_bool sp_protocol= 0, sp_protocol_enabled= 0; +static my_bool view_protocol= 0, view_protocol_enabled= 0; +static my_bool service_connection_enabled= 1; +static my_bool cursor_protocol= 0, cursor_protocol_enabled= 0; +static my_bool parsing_disabled= 0; +static my_bool display_result_vertically= FALSE, display_result_lower= FALSE, + display_metadata= FALSE, display_result_sorted= FALSE, + display_session_track_info= FALSE; +static my_bool disable_query_log= 0, disable_result_log= 0; +static my_bool disable_connect_log= 0; +static my_bool disable_warnings= 0, disable_column_names= 0; +static my_bool prepare_warnings_enabled= 0; +static my_bool disable_info= 1; +static my_bool abort_on_error= 1, opt_continue_on_error= 0; +static my_bool server_initialized= 0; +static my_bool is_windows= 0; +static my_bool optimizer_trace_active= 0; +static char **default_argv; +static const char *load_default_groups[]= +{ "mysqltest", "mariadb-test", "client", "client-server", "client-mariadb", + 0 }; +static char line_buffer[MAX_DELIMITER_LENGTH], *line_buffer_pos= line_buffer; + +/* Info on properties that can be set with --enable_X and --disable_X */ + +struct property { + my_bool *var; /* Actual variable */ + my_bool set; /* Has been set for ONE command */ + my_bool old; /* If set, thus is the old value */ + my_bool reverse; /* Variable is true if disabled */ + const char *env_name; /* Env. variable name */ +}; + +static struct property prop_list[] = { + { &abort_on_error, 0, 1, 0, "$ENABLED_ABORT_ON_ERROR" }, + { &disable_connect_log, 0, 1, 1, "$ENABLED_CONNECT_LOG" }, + { &disable_info, 0, 1, 1, "$ENABLED_INFO" }, + { &display_session_track_info, 0, 1, 1, "$ENABLED_STATE_CHANGE_INFO" }, + { &display_metadata, 0, 0, 0, "$ENABLED_METADATA" }, + { &ps_protocol_enabled, 0, 0, 0, "$ENABLED_PS_PROTOCOL" }, + { &ps2_protocol_enabled, 0, 0, 0, "$ENABLED_PS2_PROTOCOL" }, + { &view_protocol_enabled, 0, 0, 0, "$ENABLED_VIEW_PROTOCOL"}, + { &service_connection_enabled, 0, 1, 0, "$ENABLED_SERVICE_CONNECTION"}, + { &disable_query_log, 0, 0, 1, "$ENABLED_QUERY_LOG" }, + { &disable_result_log, 0, 0, 1, "$ENABLED_RESULT_LOG" }, + { &disable_warnings, 0, 0, 1, "$ENABLED_WARNINGS" } +}; + +static my_bool once_property= FALSE; + +enum enum_prop { + P_ABORT= 0, + P_CONNECT, + P_INFO, + P_SESSION_TRACK, + P_META, + P_PS, + P_PS2, + P_VIEW, + P_CONN, + P_QUERY, + P_RESULT, + P_WARN, + P_MAX +}; + +static uint start_lineno= 0; /* Start line of current command */ +static uint my_end_arg= 0; + +/* Number of lines of the result to include in failure report */ +static uint opt_tail_lines= 0; + +static uint opt_connect_timeout= 0; +static uint opt_wait_for_pos_timeout= 0; +static const uint default_wait_for_pos_timeout= 300; +static char delimiter[MAX_DELIMITER_LENGTH]= ";"; +static size_t delimiter_length= 1; + +static char TMPDIR[FN_REFLEN]; +static char global_subst_from[200]; +static char global_subst_to[200]; +static char *global_subst= NULL; +static char *read_command_buf= NULL; +static MEM_ROOT require_file_root; +static const my_bool my_true= 1; +static const my_bool my_false= 0; + +/* Block stack */ +enum block_cmd { + cmd_none, + cmd_if, + cmd_while +}; + +struct st_block +{ + int line; /* Start line of block */ + my_bool ok; /* Should block be executed */ + enum block_cmd cmd; /* Command owning the block */ + char delim[MAX_DELIMITER_LENGTH]; /* Delimiter before block */ +}; + +static struct st_block block_stack[32]; +static struct st_block *cur_block, *block_stack_end; + +/* Open file stack */ +struct st_test_file +{ + FILE* file; + char *file_name; + uint lineno; /* Current line in file */ +}; + +static struct st_test_file file_stack[16]; +static struct st_test_file* cur_file; +static struct st_test_file* file_stack_end; + +static CHARSET_INFO *charset_info= &my_charset_latin1; /* Default charset */ + +static const char *embedded_server_groups[]= +{ + "server", + "embedded", + "mysqltest_SERVER", + NullS +}; + +static int embedded_server_arg_count=0; +static char *embedded_server_args[MAX_EMBEDDED_SERVER_ARGS]; + +/* + Timer related variables + See the timer_output() definition for details +*/ +static char *timer_file = NULL; +static ulonglong timer_start; +static void timer_output(void); +static ulonglong timer_now(void); + + +static ulong connection_retry_sleep= 100000; /* Microseconds */ + +static const char *opt_plugin_dir; +static const char *opt_suite_dir, *opt_overlay_dir; +static size_t suite_dir_len, overlay_dir_len; + +/* Precompiled re's */ +static regex_t ps_re; /* the query can be run using PS protocol */ +static regex_t ps2_re; /* the query can be run using PS protocol with second execution*/ +static regex_t sp_re; /* the query can be run as a SP */ +static regex_t view_re; /* the query can be run as a view*/ + +static void init_re(void); +static int match_re(regex_t *, char *); +static void free_re(void); + +static char *get_string(char **to_ptr, char **from_ptr, + struct st_command *command); +static int replace(DYNAMIC_STRING *ds_str, + const char *search_str, size_t search_len, + const char *replace_str, size_t replace_len); + +static uint opt_protocol=0; + +DYNAMIC_ARRAY q_lines; + +#include "sslopt-vars.h" + +struct Parser +{ + int read_lines,current_line; +} parser; + +struct MasterPos +{ + char file[FN_REFLEN]; + ulong pos; +} master_pos; + +/* if set, all results are concated and compared against this file */ +const char *result_file_name= 0; + +typedef struct +{ + char *name; + size_t name_len; + char *str_val; + size_t str_val_len; + int int_val; + size_t alloced_len; + bool int_dirty; /* do not update string if int is updated until first read */ + bool is_int; + bool alloced; +} VAR; + +/*Perl/shell-like variable registers */ +VAR var_reg[10]; + +HASH var_hash; + +struct st_connection +{ + MYSQL *mysql; + /* Used when creating views and sp, to avoid implicit commit */ + MYSQL* util_mysql; + char *name; + size_t name_len; + MYSQL_STMT* stmt; + MYSQL_BIND *ps_params; + /* Set after send to disallow other queries before reap */ + my_bool pending; + +#ifdef EMBEDDED_LIBRARY + pthread_t tid; + const char *cur_query; + int cur_query_len; + int command, result; + pthread_mutex_t query_mutex; + pthread_cond_t query_cond; + pthread_mutex_t result_mutex; + pthread_cond_t result_cond; + int query_done; + my_bool has_thread; +#endif /*EMBEDDED_LIBRARY*/ +}; + +struct st_connection *connections= NULL; +struct st_connection* cur_con= NULL, *next_con, *connections_end; + +/* + List of commands in mysqltest + Must match the "command_names" array + Add new commands before Q_UNKNOWN! +*/ +enum enum_commands { + Q_CONNECTION=1, Q_QUERY, + Q_CONNECT, Q_SLEEP, Q_REAL_SLEEP, + Q_INC, Q_DEC, + Q_SOURCE, Q_DISCONNECT, + Q_LET, Q_ECHO, + Q_WHILE, Q_END_BLOCK, + Q_SYSTEM, Q_RESULT, + Q_REQUIRE, Q_SAVE_MASTER_POS, + Q_SYNC_WITH_MASTER, + Q_SYNC_SLAVE_WITH_MASTER, + Q_ERROR, + Q_SEND, Q_REAP, + Q_DIRTY_CLOSE, Q_REPLACE, Q_REPLACE_COLUMN, + Q_PING, Q_EVAL, + Q_EVALP, + Q_EVAL_RESULT, + Q_ENABLE_QUERY_LOG, Q_DISABLE_QUERY_LOG, + Q_ENABLE_RESULT_LOG, Q_DISABLE_RESULT_LOG, + Q_ENABLE_CONNECT_LOG, Q_DISABLE_CONNECT_LOG, + Q_WAIT_FOR_SLAVE_TO_STOP, + Q_ENABLE_WARNINGS, Q_DISABLE_WARNINGS, + Q_ENABLE_INFO, Q_DISABLE_INFO, + Q_ENABLE_SESSION_TRACK_INFO, Q_DISABLE_SESSION_TRACK_INFO, + Q_ENABLE_METADATA, Q_DISABLE_METADATA, + Q_ENABLE_COLUMN_NAMES, Q_DISABLE_COLUMN_NAMES, + Q_EXEC, Q_DELIMITER, + Q_DISABLE_ABORT_ON_ERROR, Q_ENABLE_ABORT_ON_ERROR, + Q_DISPLAY_VERTICAL_RESULTS, Q_DISPLAY_HORIZONTAL_RESULTS, + Q_QUERY_VERTICAL, Q_QUERY_HORIZONTAL, Q_SORTED_RESULT, + Q_LOWERCASE, + Q_START_TIMER, Q_END_TIMER, + Q_CHARACTER_SET, Q_DISABLE_PS_PROTOCOL, Q_ENABLE_PS_PROTOCOL, + Q_DISABLE_PS2_PROTOCOL, Q_ENABLE_PS2_PROTOCOL, + Q_DISABLE_VIEW_PROTOCOL, Q_ENABLE_VIEW_PROTOCOL, + Q_DISABLE_SERVICE_CONNECTION, Q_ENABLE_SERVICE_CONNECTION, + Q_ENABLE_NON_BLOCKING_API, Q_DISABLE_NON_BLOCKING_API, + Q_DISABLE_RECONNECT, Q_ENABLE_RECONNECT, + Q_IF, + Q_DISABLE_PARSING, Q_ENABLE_PARSING, + Q_REPLACE_REGEX, Q_REMOVE_FILE, Q_FILE_EXIST, + Q_WRITE_FILE, Q_COPY_FILE, Q_PERL, Q_DIE, Q_EXIT, Q_SKIP, + Q_CHMOD_FILE, Q_APPEND_FILE, Q_CAT_FILE, Q_DIFF_FILES, + Q_SEND_QUIT, Q_CHANGE_USER, Q_MKDIR, Q_RMDIR, + Q_LIST_FILES, Q_LIST_FILES_WRITE_FILE, Q_LIST_FILES_APPEND_FILE, + Q_SEND_SHUTDOWN, Q_SHUTDOWN_SERVER, + Q_RESULT_FORMAT_VERSION, + Q_MOVE_FILE, Q_REMOVE_FILES_WILDCARD, Q_SEND_EVAL, + Q_ENABLE_PREPARE_WARNINGS, Q_DISABLE_PREPARE_WARNINGS, + Q_RESET_CONNECTION, + Q_OPTIMIZER_TRACE, + Q_PS_PREPARE, + Q_PS_BIND, + Q_PS_EXECUTE, + Q_PS_CLOSE, + Q_UNKNOWN, /* Unknown command. */ + Q_COMMENT, /* Comments, ignored. */ + Q_COMMENT_WITH_COMMAND, + Q_EMPTY_LINE +}; + + +const char *command_names[]= +{ + "connection", + "query", + "connect", + "sleep", + "real_sleep", + "inc", + "dec", + "source", + "disconnect", + "let", + "echo", + "while", + "end", + "system", + "result", + "require", + "save_master_pos", + "sync_with_master", + "sync_slave_with_master", + "error", + "send", + "reap", + "dirty_close", + "replace_result", + "replace_column", + "ping", + "eval", + "evalp", + "eval_result", + /* Enable/disable that the _query_ is logged to result file */ + "enable_query_log", + "disable_query_log", + /* Enable/disable that the _result_ from a query is logged to result file */ + "enable_result_log", + "disable_result_log", + "enable_connect_log", + "disable_connect_log", + "wait_for_slave_to_stop", + "enable_warnings", + "disable_warnings", + "enable_info", + "disable_info", + "enable_session_track_info", + "disable_session_track_info", + "enable_metadata", + "disable_metadata", + "enable_column_names", + "disable_column_names", + "exec", + "delimiter", + "disable_abort_on_error", + "enable_abort_on_error", + "vertical_results", + "horizontal_results", + "query_vertical", + "query_horizontal", + "sorted_result", + "lowercase_result", + "start_timer", + "end_timer", + "character_set", + "disable_ps_protocol", + "enable_ps_protocol", + "disable_ps2_protocol", + "enable_ps2_protocol", + "disable_view_protocol", + "enable_view_protocol", + "disable_service_connection", + "enable_service_connection", + "enable_non_blocking_api", + "disable_non_blocking_api", + "disable_reconnect", + "enable_reconnect", + "if", + "disable_parsing", + "enable_parsing", + "replace_regex", + "remove_file", + "file_exists", + "write_file", + "copy_file", + "perl", + "die", + + /* Don't execute any more commands, compare result */ + "exit", + "skip", + "chmod", + "append_file", + "cat_file", + "diff_files", + "send_quit", + "change_user", + "mkdir", + "rmdir", + "list_files", + "list_files_write_file", + "list_files_append_file", + "send_shutdown", + "shutdown_server", + "result_format", + "move_file", + "remove_files_wildcard", + "send_eval", + "enable_prepare_warnings", + "disable_prepare_warnings", + "reset_connection", + "optimizer_trace", + "PS_prepare", + "PS_bind", + "PS_execute", + "PS_close", + 0 +}; + + +/* + The list of error codes to --error are stored in an internal array of + structs. This struct can hold numeric SQL error codes, error names or + SQLSTATE codes as strings. The element next to the last active element + in the list is set to type ERR_EMPTY. When an SQL statement returns an + error, we use this list to check if this is an expected error. +*/ +enum match_err_type +{ + ERR_EMPTY= 0, + ERR_ERRNO, + ERR_SQLSTATE +}; + +struct st_match_err +{ + enum match_err_type type; + union + { + uint errnum; + char sqlstate[SQLSTATE_LENGTH+1]; /* \0 terminated string */ + } code; +}; + +struct st_expected_errors +{ + struct st_match_err err[12]; + uint count; +}; +static struct st_expected_errors saved_expected_errors; + +struct st_command +{ + char *query, *query_buf,*first_argument,*last_argument,*end; + DYNAMIC_STRING content; + DYNAMIC_STRING eval_query; + int first_word_len, query_len; + my_bool abort_on_error, used_replace; + struct st_expected_errors expected_errors; + char *require_file; + enum enum_commands type; +}; + +TYPELIB command_typelib= {array_elements(command_names),"", + command_names, 0}; + +DYNAMIC_STRING ds_res; +/* Points to ds_warning in run_query, so it can be freed */ +DYNAMIC_STRING *ds_warn= 0; +struct st_command *curr_command= 0; + +char builtin_echo[FN_REFLEN]; + +struct st_replace_regex +{ +DYNAMIC_ARRAY regex_arr; /* stores a list of st_regex substitutions */ + +/* +Temporary storage areas for substitutions. To reduce unnecessary copying +and memory freeing/allocation, we pre-allocate two buffers, and alternate +their use, one for input/one for output, the roles changing on the next +st_regex substitution. At the end of substitutions buf points to the +one containing the final result. +*/ +char* buf; +char* even_buf; +char* odd_buf; +int even_buf_len; +int odd_buf_len; +}; + +struct st_replace_regex *glob_replace_regex= 0; + +struct st_replace; +struct st_replace *glob_replace= 0; +void replace_strings_append(struct st_replace *rep, DYNAMIC_STRING* ds, +const char *from); + +ATTRIBUTE_NORETURN +static void cleanup_and_exit(int exit_code); + +ATTRIBUTE_NORETURN +static void really_die(const char *msg); +void report_or_die(const char *fmt, ...); +ATTRIBUTE_NORETURN +static void die(const char *fmt, ...); +static void make_error_message(char *buf, size_t len, const char *fmt, va_list args); +ATTRIBUTE_NORETURN ATTRIBUTE_FORMAT(printf, 1, 2) +void abort_not_supported_test(const char *fmt, ...); +void verbose_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); +void log_msg(const char *fmt, ...) ATTRIBUTE_FORMAT(printf, 1, 2); + +VAR* var_from_env(const char *, const char *); +VAR* var_init(VAR* v, const char *name, size_t name_len, const char *val, size_t val_len); +VAR* var_get(const char *var_name, const char** var_name_end, + my_bool raw, my_bool ignore_not_existing); +void eval_expr(VAR* v, const char *p, const char** p_end, + bool open_end=false, bool do_eval=true); +my_bool match_delimiter(int c, const char *delim, size_t length); +void dump_result_to_reject_file(char *buf, int size); +void dump_warning_messages(); + +void do_eval(DYNAMIC_STRING *query_eval, const char *query, + const char *query_end, my_bool pass_through_escape_chars); +void str_to_file(const char *fname, char *str, size_t size); +void str_to_file2(const char *fname, char *str, size_t size, my_bool append); + +void fix_win_paths(char *val, size_t len); +const char *get_errname_from_code (uint error_code); +int multi_reg_replace(struct st_replace_regex* r,char* val); + +#ifdef _WIN32 +void free_win_path_patterns(); +#endif + + +/* For replace_column */ +static char *replace_column[MAX_COLUMNS]; +static uint max_replace_column= 0; +void do_get_replace_column(struct st_command*); +void free_replace_column(); + +/* For replace */ +void do_get_replace(struct st_command *command); +void free_replace(); + +/* For replace_regex */ +void do_get_replace_regex(struct st_command *command); +void free_replace_regex(); + +/* Used by sleep */ +void check_eol_junk_line(const char *eol); + +void free_all_replace(){ + free_replace(); + free_replace_regex(); + free_replace_column(); +} + +void var_set_int(const char* name, int value); +void enable_optimizer_trace(struct st_connection *con); +void display_optimizer_trace(struct st_connection *con, + DYNAMIC_STRING *ds); + + +class LogFile { + FILE* m_file; + char m_file_name[FN_REFLEN]; + size_t m_bytes_written; +public: + LogFile() : m_file(NULL), m_bytes_written(0) { + bzero(m_file_name, sizeof(m_file_name)); + } + + ~LogFile() { + close(); + } + + const char* file_name() const { return m_file_name; } + size_t bytes_written() const { return m_bytes_written; } + + void open(const char* dir, const char* name, const char* ext) + { + DBUG_ENTER("LogFile::open"); + DBUG_PRINT("enter", ("dir: '%s', name: '%s'", dir, name)); + if (!name) + { + m_file= stdout; + DBUG_VOID_RETURN; + } + + fn_format(m_file_name, name, dir, ext, + *dir ? MY_REPLACE_DIR | MY_REPLACE_EXT : + MY_REPLACE_EXT); + + DBUG_PRINT("info", ("file_name: %s", m_file_name)); + + if ((m_file= fopen(m_file_name, "wb+")) == NULL) + die("Failed to open log file %s, errno: %d", m_file_name, errno); + + DBUG_VOID_RETURN; + } + + void close() + { + if (m_file) { + if (m_file != stdout) + fclose(m_file); + else + fflush(m_file); + } + m_file= NULL; + } + + void flush() + { + if (m_file && m_file != stdout) + { + if (fflush(m_file)) + die("Failed to flush '%s', errno: %d", m_file_name, errno); + } + } + + void write(DYNAMIC_STRING* ds) + { + DBUG_ENTER("LogFile::write"); + DBUG_PRINT("enter", ("length: %u", (uint) ds->length)); + + DBUG_ASSERT(m_file); + + if (ds->length == 0) + DBUG_VOID_RETURN; + DBUG_ASSERT(ds->str); + +#ifdef EXTRA_DEBUG + DBUG_DUMP("extra", (uchar*) ds->str, ds->length); +#endif + + if (fwrite(ds->str, 1, ds->length, m_file) != ds->length) + die("Failed to write %lu bytes to '%s', errno: %d", + (unsigned long)ds->length, m_file_name, errno); + m_bytes_written+= ds->length; + DBUG_VOID_RETURN; + } + + void show_tail(uint lines) { + DBUG_ENTER("LogFile::show_tail"); + + if (!m_file || m_file == stdout) + DBUG_VOID_RETURN; + + if (lines == 0) + DBUG_VOID_RETURN; + lines++; + + int show_offset= 0; + char buf[256+1]; /* + zero termination for DBUG_PRINT */ + size_t bytes; + bool found_bof= false; + + /* Search backward in file until "lines" newline has been found */ + while (lines && !found_bof) + { + show_offset-= sizeof(buf)-1; + while(fseek(m_file, show_offset, SEEK_END) != 0 && show_offset < 0) + { + found_bof= true; + // Seeking before start of file + show_offset++; + } + + if ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) <= 0) + { + // ferror=0 will happen here if no queries executed yet + if (ferror(m_file)) + fprintf(stderr, + "Failed to read from '%s', errno: %d, feof:%d, ferror:%d\n", + m_file_name, errno, feof(m_file), ferror(m_file)); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", ("Read %zu bytes from file, buf: %.*s", + bytes, (int)bytes, buf)); + + char* show_from= buf + bytes; + while(show_from > buf && lines > 0 ) + { + show_from--; + if (*show_from == '\n') + lines--; + } + if (show_from != buf) + { + // The last new line was found in this buf, adjust offset + show_offset+= (int)(show_from - buf) + 1; + DBUG_PRINT("info", ("adjusted offset to %d", show_offset)); + } + DBUG_PRINT("info", ("show_offset: %d", show_offset)); + } + + fprintf(stderr, "\nThe result from queries just before the failure was:\n"); + + DBUG_PRINT("info", ("show_offset: %d", show_offset)); + if (!lines) + { + fprintf(stderr, "< snip >\n"); + + if (fseek(m_file, show_offset, SEEK_END) != 0) + { + fprintf(stderr, "Failed to seek to position %d in '%s', errno: %d", + show_offset, m_file_name, errno); + DBUG_VOID_RETURN; + } + + } + else { + DBUG_PRINT("info", ("Showing the whole file")); + if (fseek(m_file, 0L, SEEK_SET) != 0) + { + fprintf(stderr, "Failed to seek to pos 0 in '%s', errno: %d", + m_file_name, errno); + DBUG_VOID_RETURN; + } + } + + while ((bytes= fread(buf, 1, sizeof(buf)-1, m_file)) > 0) + if (bytes != fwrite(buf, 1, bytes, stderr)) + die("Failed to write to '%s', errno: %d", + m_file_name, errno); + + if (!lines) + { + fprintf(stderr, + "\nMore results from queries before failure can be found in %s\n", + m_file_name); + } + fflush(stderr); + + DBUG_VOID_RETURN; + } +}; + +LogFile log_file; +LogFile progress_file; + +void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len); +void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val); +void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val); +void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING* ds_input, + bool keep_header); + +static int match_expected_error(struct st_command *command, + unsigned int err_errno, + const char *err_sqlstate); +void handle_error(struct st_command*, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds); +void handle_no_error(struct st_command*); +void revert_properties(); + +static void handle_no_active_connection(struct st_command* command, + struct st_connection *cn, DYNAMIC_STRING *ds); + + +/* Wrapper for fgets.Strips \r off newlines on Windows. + Should be used with together with my_popen(). +*/ +static char *my_fgets(char * s, int n, FILE * stream, int *len) +{ + char *buf = fgets(s, n, stream); + if (!buf) + { + *len= 0; + return buf; + } + + *len = (int)strlen(buf); +#ifdef _WIN32 + /* Strip '\r' off newlines. */ + if (*len > 1 && buf[*len - 2] == '\r' && buf[*len - 1] == '\n') + { + buf[*len - 2]= '\n'; + buf[*len - 1]= 0; + (*len)--; + } +#endif + return buf; +} + +#ifdef EMBEDDED_LIBRARY + +#define EMB_SEND_QUERY 1 +#define EMB_READ_QUERY_RESULT 2 +#define EMB_END_CONNECTION 3 +#define EMB_PREPARE_STMT 4 +#define EMB_EXECUTE_STMT 5 +#define EMB_CLOSE_STMT 6 + +/* workaround for MySQL BUG#57491 */ +#undef MY_WME +#define MY_WME 0 + +/* attributes of the query thread */ +pthread_attr_t cn_thd_attrib; + + +/* + This procedure represents the connection and actually + runs queries when in the EMBEDDED-SERVER mode. + The run_query_normal() just sends request for running + mysql_send_query and mysql_read_query_result() here. +*/ + +pthread_handler_t connection_thread(void *arg) +{ + struct st_connection *cn= (struct st_connection*)arg; + + mysql_thread_init(); + while (cn->command != EMB_END_CONNECTION) + { + if (!cn->command) + { + pthread_mutex_lock(&cn->query_mutex); + while (!cn->command) + pthread_cond_wait(&cn->query_cond, &cn->query_mutex); + pthread_mutex_unlock(&cn->query_mutex); + } + switch (cn->command) + { + case EMB_END_CONNECTION: + goto end_thread; + case EMB_SEND_QUERY: + cn->result= mysql_send_query(cn->mysql, + cn->cur_query, cn->cur_query_len); + break; + case EMB_READ_QUERY_RESULT: + cn->result= mysql_read_query_result(cn->mysql); + break; + case EMB_PREPARE_STMT: + cn->result= mysql_stmt_prepare(cn->stmt, + cn->cur_query, cn->cur_query_len); + break; + case EMB_EXECUTE_STMT: + cn->result= mysql_stmt_execute(cn->stmt); + break; + case EMB_CLOSE_STMT: + cn->result= mysql_stmt_close(cn->stmt); + break; + default: + DBUG_ASSERT(0); + } + cn->command= 0; + pthread_mutex_lock(&cn->result_mutex); + cn->query_done= 1; + pthread_cond_signal(&cn->result_cond); + pthread_mutex_unlock(&cn->result_mutex); + } + +end_thread: + cn->query_done= 1; + mysql_close(cn->mysql); + cn->mysql= 0; + mysql_thread_end(); + pthread_exit(0); + return 0; +} + +static void wait_query_thread_done(struct st_connection *con) +{ + DBUG_ASSERT(con->has_thread); + if (!con->query_done) + { + pthread_mutex_lock(&con->result_mutex); + while (!con->query_done) + pthread_cond_wait(&con->result_cond, &con->result_mutex); + pthread_mutex_unlock(&con->result_mutex); + } +} + + +static void signal_connection_thd(struct st_connection *cn, int command) +{ + DBUG_ASSERT(cn->has_thread); + cn->query_done= 0; + cn->command= command; + pthread_mutex_lock(&cn->query_mutex); + pthread_cond_signal(&cn->query_cond); + pthread_mutex_unlock(&cn->query_mutex); +} + + +/* + Sometimes we try to execute queries when the connection is closed. + It's done to make sure it was closed completely. + So that if our connection is closed (cn->has_thread == 0), we just return + the mysql_send_query() result which is an error in this case. +*/ + +static int do_send_query(struct st_connection *cn, const char *q, int q_len) +{ + if (!cn->has_thread) + return mysql_send_query(cn->mysql, q, q_len); + cn->cur_query= q; + cn->cur_query_len= q_len; + signal_connection_thd(cn, EMB_SEND_QUERY); + return 0; +} + +static int do_read_query_result(struct st_connection *cn) +{ + DBUG_ASSERT(cn->has_thread); + wait_query_thread_done(cn); + if (cn->result) + goto exit_func; + + signal_connection_thd(cn, EMB_READ_QUERY_RESULT); + wait_query_thread_done(cn); + +exit_func: + return cn->result; +} + + +static int do_stmt_prepare(struct st_connection *cn, const char *q, int q_len) +{ + /* The cn->stmt is already set. */ + DBUG_ENTER("do_stmt_prepare"); + if (!cn->has_thread) + DBUG_RETURN(mysql_stmt_prepare(cn->stmt, q, q_len)); + cn->cur_query= q; + cn->cur_query_len= q_len; + signal_connection_thd(cn, EMB_PREPARE_STMT); + wait_query_thread_done(cn); + DBUG_RETURN(cn->result); +} + + +static int do_stmt_execute(struct st_connection *cn) +{ + DBUG_ENTER("do_stmt_execute"); + /* The cn->stmt is already set. */ + if (!cn->has_thread) + DBUG_RETURN(mysql_stmt_execute(cn->stmt)); + signal_connection_thd(cn, EMB_EXECUTE_STMT); + wait_query_thread_done(cn); + DBUG_RETURN(cn->result); +} + + +static int do_stmt_close(struct st_connection *cn) +{ + DBUG_ENTER("do_stmt_close"); + /* The cn->stmt is already set. */ + if (!cn->has_thread) + DBUG_RETURN(mysql_stmt_close(cn->stmt)); + signal_connection_thd(cn, EMB_CLOSE_STMT); + wait_query_thread_done(cn); + DBUG_RETURN(cn->result); +} + + +static void emb_close_connection(struct st_connection *cn) +{ + if (!cn->has_thread) + return; + wait_query_thread_done(cn); + signal_connection_thd(cn, EMB_END_CONNECTION); + pthread_join(cn->tid, NULL); + cn->has_thread= FALSE; + pthread_mutex_destroy(&cn->query_mutex); + pthread_cond_destroy(&cn->query_cond); + pthread_mutex_destroy(&cn->result_mutex); + pthread_cond_destroy(&cn->result_cond); +} + + +static void init_connection_thd(struct st_connection *cn) +{ + cn->query_done= 1; + cn->command= 0; + if (pthread_mutex_init(&cn->query_mutex, NULL) || + pthread_cond_init(&cn->query_cond, NULL) || + pthread_mutex_init(&cn->result_mutex, NULL) || + pthread_cond_init(&cn->result_cond, NULL) || + pthread_create(&cn->tid, &cn_thd_attrib, connection_thread, (void*)cn)) + die("Error in the thread library"); + cn->has_thread=TRUE; +} + +#else /* ! EMBEDDED_LIBRARY*/ + +#define init_connection_thd(X) do { } while(0) +#define do_send_query(cn,q,q_len) mysql_send_query(cn->mysql, q, (ulong)q_len) +#define do_read_query_result(cn) mysql_read_query_result(cn->mysql) +#define do_stmt_prepare(cn, q, q_len) mysql_stmt_prepare(cn->stmt, q, (ulong)q_len) +#define do_stmt_execute(cn) mysql_stmt_execute(cn->stmt) +#define do_stmt_close(cn) mysql_stmt_close(cn->stmt) + +#endif /*EMBEDDED_LIBRARY*/ + +void do_eval(DYNAMIC_STRING *query_eval, const char *query, + const char *query_end, my_bool pass_through_escape_chars) +{ + const char *p; + char c, next_c; + int escaped = 0; + VAR *v; + DBUG_ENTER("do_eval"); + + for (p= query; (c= *p) && p < query_end; ++p) + { + switch(c) { + case '$': + if (escaped) + { + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + } + else + { + if (!(v= var_get(p, &p, 0, 0))) + { + report_or_die( "Bad variable in eval"); + DBUG_VOID_RETURN; + } + dynstr_append_mem(query_eval, v->str_val, v->str_val_len); + } + break; + case '\\': + next_c= *(p+1); + if (escaped) + { + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + } + else if (next_c == '\\' || next_c == '$' || next_c == '"') + { + /* Set escaped only if next char is \, " or $ */ + escaped= 1; + + if (pass_through_escape_chars) + { + /* The escape char should be added to the output string. */ + dynstr_append_mem(query_eval, p, 1); + } + } + else + dynstr_append_mem(query_eval, p, 1); + break; + default: + escaped= 0; + dynstr_append_mem(query_eval, p, 1); + break; + } + } + fix_win_paths(query_eval->str, query_eval->length); + DBUG_VOID_RETURN; +} + + +/* + Show any warnings just before the error. Since the last error + is added to the warning stack, only print @@warning_count-1 warnings. + + NOTE! This function should be safe to call when an error + has occurred and this any further errors will be ignored(although logged) + + SYNOPSIS + show_warnings_before_error + mysql - connection to use + +*/ + +static void show_warnings_before_error(MYSQL* mysql) +{ + MYSQL_RES* res; + const char* query= "SHOW WARNINGS"; + DBUG_ENTER("show_warnings_before_error"); + + if (!mysql) + DBUG_VOID_RETURN; + + if (mysql_query(mysql, query)) + { + log_msg("Error running query '%s': %d %s", + query, mysql_errno(mysql), mysql_error(mysql)); + DBUG_VOID_RETURN; + } + + if ((res= mysql_store_result(mysql)) == NULL) + { + /* No result set returned */ + DBUG_VOID_RETURN; + } + + if (mysql_num_rows(res) <= 1) + { + /* Don't display the last row, it's "last error" */ + } + else + { + MYSQL_ROW row; + unsigned int row_num= 0; + unsigned int num_fields= mysql_num_fields(res); + + fprintf(stderr, "\nWarnings from just before the error:\n"); + while ((row= mysql_fetch_row(res))) + { + unsigned int i; + unsigned long *lengths= mysql_fetch_lengths(res); + + if (++row_num >= mysql_num_rows(res)) + { + /* Don't display the last row, it's "last error" */ + break; + } + + for(i= 0; i < num_fields; i++) + { + fprintf(stderr, "%.*s ", (int)lengths[i], + row[i] ? row[i] : "NULL"); + } + fprintf(stderr, "\n"); + } + } + mysql_free_result(res); + + DBUG_VOID_RETURN; +} + + +enum arg_type +{ + ARG_STRING, + ARG_REST +}; + +struct command_arg { + const char *argname; /* Name of argument */ + enum arg_type type; /* Type of argument */ + my_bool required; /* Argument required */ + DYNAMIC_STRING *ds; /* Storage for argument */ + const char *description; /* Description of the argument */ +}; + + +void check_command_args(struct st_command *command, + const char *arguments, + const struct command_arg *args, + int num_args, const char delimiter_arg) +{ + int i; + const char *ptr= arguments; + const char *start; + DBUG_ENTER("check_command_args"); + DBUG_PRINT("enter", ("num_args: %d", num_args)); + + for (i= 0; i < num_args; i++) + { + const struct command_arg *arg= &args[i]; + char delimiter; + + switch (arg->type) { + /* A string */ + case ARG_STRING: + /* Skip leading spaces */ + while (*ptr && *ptr == ' ') + ptr++; + start= ptr; + delimiter = delimiter_arg; + /* If start of arg is ' ` or " search to matching quote end instead */ + if (*ptr && strchr ("'`\"", *ptr)) + { + delimiter= *ptr; + start= ++ptr; + } + /* Find end of arg, terminated by "delimiter" */ + while (*ptr && *ptr != delimiter) + ptr++; + if (ptr > start) + { + init_dynamic_string(arg->ds, 0, ptr-start, 32); + do_eval(arg->ds, start, ptr, FALSE); + } + else + { + /* Empty string */ + init_dynamic_string(arg->ds, "", 0, 0); + } + /* Find real end of arg, terminated by "delimiter_arg" */ + /* This will do nothing if arg was not closed by quotes */ + while (*ptr && *ptr != delimiter_arg) + ptr++; + + command->last_argument= (char*)ptr; + + /* Step past the delimiter */ + if (*ptr && *ptr == delimiter_arg) + ptr++; + DBUG_PRINT("info", ("val: %s", arg->ds->str)); + break; + + /* Rest of line */ + case ARG_REST: + start= ptr; + init_dynamic_string(arg->ds, 0, command->query_len, 256); + do_eval(arg->ds, start, command->end, FALSE); + command->last_argument= command->end; + DBUG_PRINT("info", ("val: %s", arg->ds->str)); + break; + + default: + DBUG_ASSERT("Unknown argument type"); + break; + } + + /* Check required arg */ + if (arg->ds->length == 0 && arg->required) + die("Missing required argument '%s' to command '%.*b'", arg->argname, + command->first_word_len, command->query); + + } + /* Check for too many arguments passed */ + ptr= command->last_argument; + while(ptr <= command->end && *ptr != '#') + { + if (*ptr && *ptr != ' ') + die("Extra argument '%s' passed to '%.*b'", + ptr, command->first_word_len, command->query); + ptr++; + } + DBUG_VOID_RETURN; +} + +void handle_command_error(struct st_command *command, uint error, + int sys_errno) +{ + DBUG_ENTER("handle_command_error"); + DBUG_PRINT("enter", ("error: %d", error)); + var_set_int("$sys_errno",sys_errno); + var_set_int("$errno",error); + if (error != 0) + { + int i; + + if (command->abort_on_error) + { + report_or_die("command \"%.*b\" failed with error: %u my_errno: %d " + "errno: %d", + command->first_word_len, command->query, error, my_errno, + sys_errno); + DBUG_VOID_RETURN; + } + + i= match_expected_error(command, error, NULL); + + if (i >= 0) + { + DBUG_PRINT("info", ("command \"%.*s\" failed with expected error: %u, errno: %d", + command->first_word_len, command->query, error, + sys_errno)); + revert_properties(); + DBUG_VOID_RETURN; + } + if (command->expected_errors.count > 0) + report_or_die("command \"%.*b\" failed with wrong error: %u " + "my_errno: %d errno: %d", + command->first_word_len, command->query, error, my_errno, + sys_errno); + } + else if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + report_or_die("command \"%.*b\" succeeded - should have failed with " + "errno %d...", + command->first_word_len, command->query, + command->expected_errors.err[0].code.errnum); + } + revert_properties(); + DBUG_VOID_RETURN; +} + + +void close_connections() +{ + DBUG_ENTER("close_connections"); + for (--next_con; next_con >= connections; --next_con) + { + if (next_con->stmt) + do_stmt_close(next_con); +#ifdef EMBEDDED_LIBRARY + emb_close_connection(next_con); +#endif + next_con->stmt= 0; + mysql_close(next_con->mysql); + next_con->mysql= 0; + if (next_con->util_mysql) + mysql_close(next_con->util_mysql); + my_free(next_con->name); + } + my_free(connections); + DBUG_VOID_RETURN; +} + +void close_util_connections() +{ + DBUG_ENTER("close_util_connections"); + if (cur_con->util_mysql) + { + mysql_close(cur_con->util_mysql); + cur_con->util_mysql = 0; + } + DBUG_VOID_RETURN; +} + +void close_statements() +{ + struct st_connection *con; + DBUG_ENTER("close_statements"); + for (con= connections; con < next_con; con++) + { + if (con->stmt) + do_stmt_close(con); + con->stmt= 0; + } + DBUG_VOID_RETURN; +} + + +void close_files() +{ + DBUG_ENTER("close_files"); + for (; cur_file >= file_stack; cur_file--) + { + if (cur_file->file && cur_file->file != stdin) + { + DBUG_PRINT("info", ("closing file: %s", cur_file->file_name)); + fclose(cur_file->file); + } + my_free(cur_file->file_name); + cur_file->file_name= 0; + } + DBUG_VOID_RETURN; +} + + +void free_used_memory() +{ + uint i; + DBUG_ENTER("free_used_memory"); + + if (connections) + close_connections(); + close_files(); + my_hash_free(&var_hash); + + for (i= 0 ; i < q_lines.elements ; i++) + { + struct st_command **q= dynamic_element(&q_lines, i, struct st_command**); + my_free((*q)->query_buf); + if ((*q)->eval_query.str) + dynstr_free(&(*q)->eval_query); + if ((*q)->content.str) + dynstr_free(&(*q)->content); + my_free((*q)); + } + for (i= 0; i < 10; i++) + { + if (var_reg[i].alloced_len) + my_free(var_reg[i].str_val); + } + while (embedded_server_arg_count > 1) + my_free(embedded_server_args[--embedded_server_arg_count]); + delete_dynamic(&q_lines); + dynstr_free(&ds_res); + if (ds_warn) + dynstr_free(ds_warn); + free_all_replace(); + my_free(opt_pass); + free_defaults(default_argv); + free_root(&require_file_root, MYF(0)); + free_re(); + my_free(read_command_buf); +#ifdef _WIN32 + free_win_path_patterns(); +#endif + DBUG_VOID_RETURN; +} + + +#ifdef EMBEDDED_LIBRARY +void ha_pre_shutdown(); +#endif + + +ATTRIBUTE_NORETURN static void cleanup_and_exit(int exit_code) +{ +#ifdef EMBEDDED_LIBRARY + if (server_initialized) + ha_pre_shutdown(); +#endif + + free_used_memory(); + + /* Only call mysql_server_end if mysql_server_init has been called */ + if (server_initialized) + mysql_server_end(); + + /* + mysqltest is fundamentally written in a way that makes impossible + to free all memory before exit (consider memory allocated + for frame local DYNAMIC_STRING's and die() invoked down the stack. + + We close stderr here to stop unavoidable safemalloc reports + from polluting the output. + */ + fclose(stderr); + + my_end(my_end_arg); + + if (!silent) { + switch (exit_code) { + case 1: + printf("not ok\n"); + break; + case 0: + printf("ok\n"); + break; + case 62: + printf("skipped\n"); + break; + default: + printf("unknown exit code: %d\n", exit_code); + DBUG_ASSERT(0); + } + } + + exit(exit_code); +} + +size_t print_file_stack(char *s, const char *end) +{ + char *start= s; + struct st_test_file* err_file= cur_file; + if (err_file == file_stack) + return 0; + + for (;;) + { + err_file--; + s+= my_snprintf(s, end - s, "included from %s at line %d:\n", + err_file->file_name, err_file->lineno); + if (err_file == file_stack) + break; + } + return s - start; +} + + +static void make_error_message(char *buf, size_t len, const char *fmt, va_list args) +{ + char *s= buf, *end= buf + len; + s+= my_snprintf(s, end - s, "mysqltest: "); + if (cur_file && cur_file != file_stack) + { + s+= my_snprintf(s, end - s, "In included file \"%s\": \n", + cur_file->file_name); + s+= print_file_stack(s, end); + } + + if (start_lineno > 0) + s+= my_snprintf(s, end -s, "At line %u: ", start_lineno); + if (!fmt) + fmt= "unknown error"; + + s+= my_vsnprintf(s, end - s, fmt, args); + s+= my_snprintf(s, end -s, "\n"); +} + +static void die(const char *fmt, ...) +{ + char buff[DIE_BUFF_SIZE]; + va_list args; + DBUG_ENTER("die"); + + va_start(args, fmt); + make_error_message(buff, sizeof(buff), fmt, args); + really_die(buff); +} + +static void really_die(const char *msg) +{ + static int dying= 0; + fflush(stdout); + fprintf(stderr, "%s", msg); + fflush(stderr); + + /* + Protect against dying twice + first time 'die' is called, try to write log files + second time, just exit + */ + if (dying) + cleanup_and_exit(1); + dying= 1; + + log_file.show_tail(opt_tail_lines); + + /* + Help debugging by displaying any warnings that might have + been produced prior to the error + */ + if (cur_con && !cur_con->pending) + show_warnings_before_error(cur_con->mysql); + + cleanup_and_exit(1); +} + +void report_or_die(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("report_or_die"); + + char buff[DIE_BUFF_SIZE]; + + va_start(args, fmt); + make_error_message(buff, sizeof(buff), fmt, args); + va_end(args); + + if (opt_continue_on_error) + { + /* Just log the error and continue */ + replace_dynstr_append(&ds_res, buff); + error_count++; + DBUG_VOID_RETURN; + } + + really_die(buff); +} + + +void abort_not_supported_test(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("abort_not_supported_test"); + + /* Print include filestack */ + fflush(stdout); + fprintf(stderr, "The test '%s' is not supported by this installation\n", + file_stack->file_name); + fprintf(stderr, "Detected in file %s at line %d\n", + cur_file->file_name, cur_file->lineno); + + char buff[DIE_BUFF_SIZE]; + buff[0] = '\0'; + print_file_stack(buff, buff + sizeof(buff)); + fprintf(stderr, "%s", buff); + + /* Print error message */ + va_start(args, fmt); + if (fmt) + { + fprintf(stderr, "reason: "); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + fflush(stderr); + } + va_end(args); + + cleanup_and_exit(62); +} + + +void abort_not_in_this_version() +{ + die("Not available in this version of mysqltest"); +} + + +void verbose_msg(const char *fmt, ...) +{ + va_list args; + DBUG_ENTER("verbose_msg"); + DBUG_PRINT("enter", ("format: %s", fmt)); + + if (!verbose) + DBUG_VOID_RETURN; + + fflush(stdout); + va_start(args, fmt); + fprintf(stderr, "mysqltest: "); + if (cur_file && cur_file != file_stack) + fprintf(stderr, "In included file \"%s\": ", + cur_file->file_name); + if (start_lineno != 0) + fprintf(stderr, "At line %u: ", start_lineno); + vfprintf(stderr, fmt, args); + fprintf(stderr, "\n"); + va_end(args); + fflush(stderr); + + DBUG_VOID_RETURN; +} + + +void log_msg(const char *fmt, ...) +{ + va_list args; + char buff[1024]; + size_t len; + DBUG_ENTER("log_msg"); + + va_start(args, fmt); + len= my_vsnprintf(buff, sizeof(buff)-1, fmt, args); + va_end(args); + + dynstr_append_mem(&ds_res, buff, len); + dynstr_append(&ds_res, "\n"); + + DBUG_VOID_RETURN; +} + + +/* + Read a file and append it to ds + + SYNOPSIS + cat_file + ds - pointer to dynamic string where to add the files content + filename - name of the file to read + +*/ + +int cat_file(DYNAMIC_STRING* ds, const char* filename) +{ + int fd; + size_t len; + char *buff; + + if ((fd= my_open(filename, O_RDONLY, MYF(0))) < 0) + return 1; + + len= (size_t) my_seek(fd, 0, SEEK_END, MYF(0)); + my_seek(fd, 0, SEEK_SET, MYF(0)); + if (len == (size_t)MY_FILEPOS_ERROR || + !(buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, len + 1, + MYF(MY_WME|MY_FAE)))) + { + my_close(fd, MYF(0)); + return 1; + } + len= my_read(fd, (uchar*)buff, len, MYF(0)); + my_close(fd, MYF(0)); + + { + char *p= buff, *start= buff,*end=buff+len; + while (p < end) + { + /* Convert cr/lf to lf */ + if (*p == '\r' && p+1 < end && *(p+1)== '\n') + { + /* Add fake newline instead of cr and output the line */ + *p= '\n'; + p++; /* Step past the "fake" newline */ + *p= 0; + replace_dynstr_append_mem(ds, start, p-start); + p++; /* Step past the "fake" newline */ + start= p; + } + else + p++; + } + /* Output any chars that migh be left */ + *p= 0; + replace_dynstr_append_mem(ds, start, p-start); + } + my_free(buff); + return 0; +} + + +/* + Run the specified command with popen + + SYNOPSIS + run_command + cmd - command to execute(should be properly quoted + ds_res- pointer to dynamic string where to store the result + +*/ + +static int run_command(char* cmd, + DYNAMIC_STRING *ds_res) +{ + char buf[512]= {0}; + FILE *res_file; + int error; + DBUG_ENTER("run_command"); + DBUG_PRINT("enter", ("cmd: %s", cmd)); + + if (!(res_file= my_popen(cmd, "r"))) + { + report_or_die("popen(\"%s\", \"r\") failed", cmd); + DBUG_RETURN(-1); + } + + int len; + while (my_fgets(buf, sizeof(buf), res_file, &len)) + { + DBUG_PRINT("info", ("buf: %s", buf)); + if(ds_res) + { + /* Save the output of this command in the supplied string */ + dynstr_append_mem(ds_res, buf,len); + } + else + { + /* Print it directly on screen */ + fprintf(stdout, "%s", buf); + } + } + + error= my_pclose(res_file); + DBUG_RETURN(WEXITSTATUS(error)); +} + + +/* + Run the specified tool with variable number of arguments + + SYNOPSIS + run_tool + tool_path - the name of the tool to run + ds_res - pointer to dynamic string where to store the result + ... - variable number of arguments that will be properly + quoted and appended after the tool's name + +*/ + +static int run_tool(const 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 be os quoted */ + if (strncmp(arg, "--", 2) == 0) + dynstr_append_os_quoted(&ds_cmdline, arg, NullS); + else + 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); +} + + +/* + Test if diff is present. This is needed on Windows systems + as the OS returns 1 whether diff is successful or if it is + not present. + + We run diff -v and look for output in stdout. + We don't redirect stderr to stdout to make for a simplified check + Windows will output '"diff"' is not recognized... to stderr if it is + not present. +*/ + +#ifdef _WIN32 + +static int diff_check(const char *diff_name) +{ + FILE *res_file; + char buf[128]; + int have_diff= 0; + + my_snprintf(buf, sizeof(buf), "%s -v", diff_name); + + if (!(res_file= my_popen(buf, "r"))) + die("popen(\"%s\", \"r\") failed", buf); + + /* + if diff is not present, nothing will be in stdout to increment + have_diff + */ + int len; + if (my_fgets(buf, sizeof(buf), res_file, &len)) + have_diff= 1; + + pclose(res_file); + + return have_diff; +} + +#endif + + +/* + Show the diff of two files using the systems builtin diff + command. If no such diff command exist, just dump the content + of the two files and inform about how to get "diff" + + SYNOPSIS + show_diff + ds - pointer to dynamic string where to add the diff(may be NULL) + filename1 - name of first file + filename2 - name of second file + +*/ + +void show_diff(DYNAMIC_STRING* ds, + const char* filename1, const char* filename2) +{ + DYNAMIC_STRING ds_tmp; + const char *diff_name = 0; + + if (init_dynamic_string(&ds_tmp, "", 256, 256)) + die("Out of memory"); + + /* determine if we have diff on Windows + needs special processing due to return values + on that OS + This test is only done on Windows since it's only needed there + in order to correctly detect non-availability of 'diff', and + the way it's implemented does not work with default 'diff' on Solaris. + */ +#ifdef _WIN32 + if (diff_check("diff")) + diff_name = "diff"; + else if (diff_check("mtrdiff")) + diff_name = "mtrdiff"; + else + diff_name = 0; +#else + diff_name = "diff"; /* Otherwise always assume it's called diff */ +#endif + + if (diff_name) + { + /* First try with unified diff */ + if (run_tool(diff_name, + &ds_tmp, /* Get output from diff in ds_tmp */ + "-u", + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + dynstr_set(&ds_tmp, ""); + + /* Fallback to context diff with "diff -c" */ + if (run_tool(diff_name, + &ds_tmp, /* Get output from diff in ds_tmp */ + "-c", + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + dynstr_set(&ds_tmp, ""); + + /* Fallback to simple diff with "diff" */ + if (run_tool(diff_name, + &ds_tmp, /* Get output from diff in ds_tmp */ + filename1, + filename2, + "2>&1", + NULL) > 1) /* Most "diff" tools return >1 if error */ + { + diff_name= 0; + } + } + } + } + + if (! diff_name) + { + /* + Fallback to dump both files to result file and inform + about installing "diff" + */ + dynstr_append(&ds_tmp, "\n"); + dynstr_append(&ds_tmp, +"\n" +"The two files differ but it was not possible to execute 'diff' in\n" +"order to show only the difference. Instead the whole content of the\n" +"two files was shown for you to diff manually.\n\n" +"To get a better report you should install 'diff' on your system, which you\n" +"for example can get from http://www.gnu.org/software/diffutils/diffutils.html\n" +#ifdef _WIN32 +"or http://gnuwin32.sourceforge.net/packages/diffutils.htm\n" +#endif +"\n"); + + dynstr_append(&ds_tmp, " --- "); + dynstr_append(&ds_tmp, filename1); + dynstr_append(&ds_tmp, " >>>\n"); + cat_file(&ds_tmp, filename1); + dynstr_append(&ds_tmp, "<<<\n --- "); + dynstr_append(&ds_tmp, filename1); + dynstr_append(&ds_tmp, " >>>\n"); + cat_file(&ds_tmp, filename2); + dynstr_append(&ds_tmp, "<<<<\n"); + } + + if (ds) + { + /* Add the diff to output */ + dynstr_append_mem(ds, ds_tmp.str, ds_tmp.length); + } + else + { + /* Print diff directly to stdout */ + fprintf(stderr, "%s\n", ds_tmp.str); + } + + dynstr_free(&ds_tmp); + +} + + +enum compare_files_result_enum { + RESULT_OK= 0, + RESULT_CONTENT_MISMATCH= 1, + RESULT_LENGTH_MISMATCH= 2 +}; + +/* + Compare two files, given a fd to the first file and + name of the second file + + SYNOPSIS + compare_files2 + fd - Open file descriptor of the first file + filename2 - Name of second file + + RETURN VALUES + According to the values in "compare_files_result_enum" + +*/ + +int compare_files2(File fd1, const char* filename2) +{ + int error= RESULT_OK; + File fd2; + size_t fd1_length, fd2_length; + DYNAMIC_STRING fd1_result, fd2_result; + + if ((fd2= my_open(filename2, O_RDONLY, MYF(0))) < 0) + { + my_close(fd1, MYF(0)); + die("Failed to open second file: '%s'", filename2); + } + + fd1_length= (size_t) my_seek(fd1, 0, SEEK_END, MYF(0)); + fd2_length= (size_t) my_seek(fd2, 0, SEEK_END, MYF(0)); + + if (init_dynamic_string(&fd1_result, 0, fd1_length, 0) || + init_dynamic_string(&fd2_result, 0, fd2_length, 0)) + die("Out of memory when allocating data for result"); + + fd1_result.length= fd1_length; + fd2_result.length= fd2_length; + + (void) my_seek(fd1, 0, SEEK_SET, MYF(0)); + (void) my_seek(fd2, 0, SEEK_SET, MYF(0)); + if (my_read(fd1, (uchar*) fd1_result.str, fd1_length, MYF(MY_WME | MY_NABP))) + die("Error when reading data from result file"); + if (my_read(fd2, (uchar*) fd2_result.str, fd2_length, MYF(MY_WME | MY_NABP))) + die("Error when reading data from result file"); + + if (global_subst && + (fd1_length != fd2_length || + memcmp(fd1_result.str, fd2_result.str, fd1_length))) + { + /** + @todo MARIA_HACK + This serves for when a test is run with --default-storage-engine=X + where X is not MyISAM: tests using SHOW CREATE TABLE will always fail + because SHOW CREATE TABLE prints X instead of MyISAM. With + --global-subst=X,MyISAM , such trivial differences are eliminated and + test may be reported as passing. + --global-subst is only a quick way to run a lot of existing tests + with Maria and find bugs; it is not good enough for reaching the main + trees when Maria is merged into them. + --global-subst should be removed. + */ + size_t global_subst_from_len= strlen(global_subst_from); + size_t global_subst_to_len= strlen(global_subst_to); + while (replace(&fd1_result, + global_subst_from, global_subst_from_len, + global_subst_to, global_subst_to_len) == 0) + /* do nothing */ ; + /* let's compare again to see if it is ok now */ + } + + if (fd1_result.length != fd2_result.length) + error= RESULT_LENGTH_MISMATCH; + else if ((memcmp(fd1_result.str, fd2_result.str, fd1_result.length))) + error= RESULT_CONTENT_MISMATCH; + + my_close(fd2, MYF(0)); + dynstr_free(&fd1_result); + dynstr_free(&fd2_result); + + return error; +} + + +/* + Compare two files, given their filenames + + SYNOPSIS + compare_files + filename1 - Name of first file + filename2 - Name of second file + + RETURN VALUES + See 'compare_files2' + +*/ + +int compare_files(const char* filename1, const char* filename2) +{ + File fd; + int error; + + if ((fd= my_open(filename1, O_RDONLY, MYF(0))) < 0) + die("Failed to open first file: '%s'", filename1); + + error= compare_files2(fd, filename2); + + my_close(fd, MYF(0)); + + return error; +} + + +/* + Compare content of the string in ds to content of file fname + + SYNOPSIS + dyn_string_cmp + ds - Dynamic string containing the string o be compared + fname - Name of file to compare with + + RETURN VALUES + See 'compare_files2' +*/ + +int dyn_string_cmp(DYNAMIC_STRING* ds, const char *fname) +{ + int error; + File fd; + char temp_file_path[FN_REFLEN]; + + DBUG_ENTER("dyn_string_cmp"); + DBUG_PRINT("enter", ("fname: %s", fname)); + + if ((fd= create_temp_file(temp_file_path, TMPDIR, "tmp", O_SHARE, + MYF(MY_WME))) < 0) + die("Failed to create temporary file for ds"); + + /* Write ds to temporary file and set file pos to beginning*/ + if (my_write(fd, (uchar *) ds->str, ds->length, + MYF(MY_FNABP | MY_WME)) || + my_seek(fd, 0, SEEK_SET, MYF(0)) == MY_FILEPOS_ERROR) + { + my_close(fd, MYF(0)); + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(MY_WME)); + die("Failed to write file '%s'", temp_file_path); + } + + error= compare_files2(fd, fname); + + my_close(fd, MYF(0)); + /* Remove the temporary file */ + my_delete(temp_file_path, MYF(MY_WME)); + + DBUG_RETURN(error); +} + + +/* + Check the content of log against result file + + SYNOPSIS + check_result + + RETURN VALUES + error - the function will not return + +*/ + +void check_result() +{ + const char *mess= 0; + + DBUG_ENTER("check_result"); + DBUG_ASSERT(result_file_name); + DBUG_PRINT("enter", ("result_file_name: %s", result_file_name)); + + switch (compare_files(log_file.file_name(), result_file_name)) { + case RESULT_OK: + if (!error_count) + break; /* ok */ + mess= "Got errors while running test"; + /* Fallthrough */ + case RESULT_LENGTH_MISMATCH: + if (!mess) + mess= "Result length mismatch\n"; + /* Fallthrough */ + case RESULT_CONTENT_MISMATCH: + { + /* + Result mismatched, dump results to .reject file + and then show the diff + */ + char reject_file[FN_REFLEN]; + size_t reject_length; + + if (!mess) + mess= "Result content mismatch\n"; + + dirname_part(reject_file, result_file_name, &reject_length); + + if (access(reject_file, W_OK) == 0) + { + /* Result file directory is writable, save reject file there */ + fn_format(reject_file, result_file_name, "", + ".reject", MY_REPLACE_EXT); + } + else + { + /* Put reject file in opt_logdir */ + fn_format(reject_file, result_file_name, opt_logdir, + ".reject", MY_REPLACE_DIR | MY_REPLACE_EXT); + } + + if (my_copy(log_file.file_name(), reject_file, MYF(0)) != 0) + die("Failed to copy '%s' to '%s', errno: %d", + log_file.file_name(), reject_file, errno); + + show_diff(NULL, result_file_name, reject_file); + die("%s", mess); + break; + } + default: /* impossible */ + die("Unknown error code from dyn_string_cmp()"); + } + + DBUG_VOID_RETURN; +} + + +/* + Check the content of ds against a require file + If match fails, abort the test with special error code + indicating that test is not supported + + SYNOPSIS + check_require + ds - content to be checked + fname - name of file to check against + + RETURN VALUES + error - the function will not return + +*/ + +void check_require(DYNAMIC_STRING* ds, const char *fname) +{ + DBUG_ENTER("check_require"); + + if (dyn_string_cmp(ds, fname)) + { + char reason[FN_REFLEN]; + fn_format(reason, fname, "", "", MY_REPLACE_EXT | MY_REPLACE_DIR); + abort_not_supported_test("Test requires: '%s'", reason); + } + DBUG_VOID_RETURN; +} + + +/* + Remove surrounding chars from string + + Return 1 if first character is found but not last +*/ +static int strip_surrounding(char* str, char c1, char c2) +{ + char* ptr= str; + + /* Check if the first non space character is c1 */ + while(*ptr && my_isspace(charset_info, *ptr)) + ptr++; + if (*ptr == c1) + { + /* Replace it with a space */ + *ptr= ' '; + + /* Last non space character should be c2 */ + ptr= strend(str)-1; + while(*ptr && my_isspace(charset_info, *ptr)) + ptr--; + if (*ptr == c2) + { + /* Replace it with \0 */ + *ptr= 0; + } + else + { + /* Mismatch detected */ + return 1; + } + } + return 0; +} + + +static void strip_parentheses(struct st_command *command) +{ + if (strip_surrounding(command->first_argument, '(', ')')) + die("%.*b - argument list started with '%c' must be ended with '%c'", + command->first_word_len, command->query, '(', ')'); +} + + +C_MODE_START + +static uchar *get_var_key(const uchar* var, size_t *len, + my_bool __attribute__((unused)) t) +{ + char* key; + key = ((VAR*)var)->name; + *len = ((VAR*)var)->name_len; + return (uchar*)key; +} + + +static void var_free(void *v) +{ + VAR *var= (VAR*) v; + my_free(var->str_val); + if (var->alloced) + my_free(var); +} + +C_MODE_END + +void var_check_int(VAR *v) +{ + char *endptr; + char *str= v->str_val; + + /* Initially assume not a number */ + v->int_val= 0; + v->is_int= false; + v->int_dirty= false; + if (!str) return; + + v->int_val = (int) strtol(str, &endptr, 10); + /* It is an int if strtol consumed something up to end/space/tab */ + if (endptr > str && (!*endptr || *endptr == ' ' || *endptr == '\t')) + v->is_int= true; +} + + +VAR *var_init(VAR *v, const char *name, size_t name_len, const char *val, size_t val_len) +{ + size_t val_alloc_len; + VAR *tmp_var; + if (!name_len && name) + name_len = strlen(name); + if (!val_len && val) + val_len = strlen(val) ; + if (!val) + val_len= 0; + val_alloc_len = val_len + 16; /* room to grow */ + if (!(tmp_var=v) && !(tmp_var = (VAR*)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*tmp_var) + + name_len+2, MYF(MY_WME|MY_FAE)))) + die("Out of memory"); + + if (name != NULL) + { + tmp_var->name= reinterpret_cast<char*>(tmp_var) + sizeof(*tmp_var); + memcpy(tmp_var->name, name, name_len); + tmp_var->name[name_len]= 0; + } + else + tmp_var->name= NULL; + + tmp_var->alloced = (v == 0); + + if (!(tmp_var->str_val = (char*)my_malloc(PSI_NOT_INSTRUMENTED, + val_alloc_len+1, MYF(MY_WME|MY_FAE)))) + die("Out of memory"); + + if (val) + memcpy(tmp_var->str_val, val, val_len); + tmp_var->str_val[val_len]= 0; + + var_check_int(tmp_var); + tmp_var->name_len = name_len; + tmp_var->str_val_len = val_len; + tmp_var->alloced_len = val_alloc_len; + return tmp_var; +} + + +VAR* var_from_env(const char *name, const char *def_val) +{ + const char *tmp; + VAR *v; + if (!(tmp = getenv(name))) + tmp = def_val; + + v = var_init(0, name, strlen(name), tmp, strlen(tmp)); + my_hash_insert(&var_hash, (uchar*)v); + return v; +} + + +VAR* var_get(const char *var_name, const char **var_name_end, my_bool raw, + my_bool ignore_not_existing) +{ + int digit; + VAR *v; + DBUG_ENTER("var_get"); + DBUG_PRINT("enter", ("var_name: %s",var_name)); + + if (*var_name != '$') + goto err; + digit = *++var_name - '0'; + if (digit < 0 || digit >= 10) + { + const char *save_var_name = var_name, *end; + uint length; + end = (var_name_end) ? *var_name_end : 0; + while (my_isvar(charset_info,*var_name) && var_name != end) + var_name++; + if (var_name == save_var_name) + { + if (ignore_not_existing) + DBUG_RETURN(0); + die("Empty variable"); + } + length= (uint) (var_name - save_var_name); + if (length >= MAX_VAR_NAME_LENGTH) + die("Too long variable name: %s", save_var_name); + + if (!(v = (VAR*) my_hash_search(&var_hash, (const uchar*) save_var_name, + length))) + { + char buff[MAX_VAR_NAME_LENGTH+1]; + strmake(buff, save_var_name, length); + v= var_from_env(buff, ""); + } + var_name--; /* Point at last character */ + } + else + v = var_reg + digit; + + if (!raw && v->int_dirty) + { + sprintf(v->str_val, "%d", v->int_val); + v->int_dirty= false; + v->str_val_len = strlen(v->str_val); + } + if (var_name_end) + *var_name_end = var_name ; + DBUG_RETURN(v); +err: + if (var_name_end) + *var_name_end = 0; + die("Unsupported variable name: %s", var_name); + DBUG_RETURN(0); +} + + +VAR *var_obtain(const char *name, int len) +{ + VAR* v; + if ((v = (VAR*)my_hash_search(&var_hash, (const uchar *) name, len))) + return v; + v = var_init(0, name, len, "", 0); + my_hash_insert(&var_hash, (uchar*)v); + return v; +} + + +/* + - if variable starts with a $ it is regarded as a local test variable + - if not it is treated as a environment variable, and the corresponding + environment variable will be updated +*/ + +void var_set(const char *var_name, const char *var_name_end, + const char *var_val, const char *var_val_end) +{ + int digit, env_var= 0; + VAR *v; + DBUG_ENTER("var_set"); + DBUG_PRINT("enter", ("var_name: '%.*s' = '%.*s' (length: %d)", + (int) (var_name_end - var_name), var_name, + (int) (var_val_end - var_val), var_val, + (int) (var_val_end - var_val))); + + if (*var_name != '$') + env_var= 1; + else + var_name++; + + digit= *var_name - '0'; + if (!(digit < 10 && digit >= 0)) + { + v= var_obtain(var_name, (uint) (var_name_end - var_name)); + } + else + v= var_reg + digit; + + eval_expr(v, var_val, (const char**) &var_val_end); + + if (env_var) + { + if (v->int_dirty) + { + sprintf(v->str_val, "%d", v->int_val); + v->int_dirty=false; + v->str_val_len= strlen(v->str_val); + } + /* setenv() expects \0-terminated strings */ + DBUG_ASSERT(v->name[v->name_len] == 0); + setenv(v->name, v->str_val, 1); + } + DBUG_VOID_RETURN; +} + + +void var_set_string(const char* name, const char* value) +{ + var_set(name, name + strlen(name), value, value + strlen(value)); +} + + +void var_set_int(const char* name, int value) +{ + char buf[21]; + my_snprintf(buf, sizeof(buf), "%d", value); + var_set_string(name, buf); +} + + +/* + Store an integer (typically the returncode of the last SQL) + statement in the mysqltest builtin variable $mysql_errno +*/ + +void var_set_errno(int sql_errno) +{ + var_set_int("$mysql_errno", sql_errno); + var_set_string("$mysql_errname", get_errname_from_code(sql_errno)); +} + +/* Functions to handle --disable and --enable properties */ + +void set_once_property(enum_prop prop, my_bool val) +{ + property &pr= prop_list[prop]; + pr.set= 1; + pr.old= *pr.var; + *pr.var= val; + var_set_int(pr.env_name, (val != pr.reverse)); + once_property= TRUE; +} + +void set_property(st_command *command, enum_prop prop, my_bool val) +{ + char* p= command->first_argument; + if (p && !strcmp (p, "ONCE")) + { + command->last_argument= p + 4; + set_once_property(prop, val); + return; + } + property &pr= prop_list[prop]; + *pr.var= val; + pr.set= 0; + var_set_int(pr.env_name, (val != pr.reverse)); +} + +void revert_properties() +{ + if (! once_property) + return; + for (int i= 0; i < (int) P_MAX; i++) + { + property &pr= prop_list[i]; + if (pr.set) + { + *pr.var= pr.old; + pr.set= 0; + var_set_int(pr.env_name, (pr.old != pr.reverse)); + } + } + once_property=FALSE; +} + + +/* + Set variable from the result of a query + + SYNOPSIS + var_query_set() + var variable to set from query + query start of query string to execute + query_end end of the query string to execute + + + DESCRIPTION + let @<var_name> = `<query>` + + Execute the query and assign the first row of result to var as + a tab separated strings + + Also assign each column of the result set to + variable "$<var_name>_<column_name>" + Thus the tab separated output can be read from $<var_name> and + and each individual column can be read as $<var_name>_<col_name> + +*/ + +void var_query_set(VAR *var, const char *query, const char** query_end) +{ + char *end = (char*)((query_end && *query_end) ? + *query_end : query + strlen(query)); + MYSQL_RES *UNINIT_VAR(res); + MYSQL_ROW row; + MYSQL* mysql = cur_con->mysql; + DYNAMIC_STRING ds_query; + DBUG_ENTER("var_query_set"); + + if (!mysql) + { + struct st_command command; + DBUG_ASSERT(query_end); + memset(&command, 0, sizeof(command)); + command.query= (char*)query; + command.first_word_len= (int)(*query_end - query); + command.first_argument= command.query + command.first_word_len; + command.end= (char*)*query_end; + command.abort_on_error= 1; /* avoid uninitialized variables */ + handle_no_active_connection(&command, cur_con, &ds_res); + DBUG_VOID_RETURN; + } + + /* Only white space or ) allowed past ending ` */ + while (end > query && *end != '`') + { + if (*end && (*end != ' ' && *end != '\t' && *end != '\n' && *end != ')')) + die("Spurious text after `query` expression"); + --end; + } + + if (query == end) + die("Syntax error in query, missing '`'"); + ++query; + + /* Eval the query, thus replacing all environment variables */ + init_dynamic_string(&ds_query, 0, (end - query) + 32, 256); + do_eval(&ds_query, query, end, FALSE); + + if (mysql_real_query(mysql, ds_query.str, (ulong)ds_query.length) || + !(res= mysql_store_result(mysql))) + { + handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), &ds_res); + /* If error was acceptable, return empty string */ + dynstr_free(&ds_query); + eval_expr(var, "", 0); + DBUG_VOID_RETURN; + } + + dynstr_free(&ds_query); + + if ((row= mysql_fetch_row(res)) && row[0]) + { + /* + Concatenate all fields in the first row with tab in between + and assign that string to the $variable + */ + DYNAMIC_STRING result; + uint i; + ulong *lengths; + + init_dynamic_string(&result, "", 512, 512); + lengths= mysql_fetch_lengths(res); + for (i= 0; i < mysql_num_fields(res); i++) + { + if (row[i]) + { + /* Add column to tab separated string */ + char *val= row[i]; + size_t len= lengths[i]; + + if (glob_replace_regex) + { + /* Regex replace */ + if (!multi_reg_replace(glob_replace_regex, (char*)val)) + { + val= glob_replace_regex->buf; + len= strlen(val); + } + } + + if (glob_replace) + replace_strings_append(glob_replace, &result, val); + else + dynstr_append_mem(&result, val, len); + } + dynstr_append_mem(&result, "\t", 1); + } + end= result.str + result.length-1; + /* Evaluation should not recurse via backtick */ + eval_expr(var, result.str, (const char**) &end, false, false); + dynstr_free(&result); + } + else + eval_expr(var, "", 0); + + mysql_free_result(res); + DBUG_VOID_RETURN; +} + + +static void +set_result_format_version(ulong new_version) +{ + switch (new_version){ + case 1: + /* The first format */ + break; + case 2: + /* New format that also writes comments and empty lines + from test file to result */ + break; + default: + die("Version format %lu has not yet been implemented", new_version); + break; + } + opt_result_format_version= new_version; +} + + +/* + Set the result format version to use when generating + the .result file +*/ + +static void +do_result_format_version(struct st_command *command) +{ + long version; + static DYNAMIC_STRING ds_version; + const struct command_arg result_format_args[] = { + {"version", ARG_STRING, TRUE, &ds_version, "Version to use"} + }; + + DBUG_ENTER("do_result_format_version"); + + check_command_args(command, command->first_argument, + result_format_args, + sizeof(result_format_args)/sizeof(struct command_arg), + ','); + + /* Convert version number to int */ + if (!str2int(ds_version.str, 10, (long) 0, (long) INT_MAX, &version)) + die("Invalid version number: '%s'", ds_version.str); + + set_result_format_version(version); + + dynstr_append(&ds_res, "result_format: "); + dynstr_append_mem(&ds_res, ds_version.str, ds_version.length); + dynstr_append(&ds_res, "\n"); + dynstr_free(&ds_version); +} + + +/* + Set variable from the result of a field in a query + + This function is useful when checking for a certain value + in the output from a query that can't be restricted to only + return some values. A very good example of that is most SHOW + commands. + + SYNOPSIS + var_set_query_get_value() + + DESCRIPTION + let $variable= query_get_value(<query to run>,<column name>,<row no>); + + <query to run> - The query that should be sent to the server + <column name> - Name of the column that holds the field be compared + against the expected value + <row no> - Number of the row that holds the field to be + compared against the expected value + +*/ + +void var_set_query_get_value(struct st_command *command, VAR *var) +{ + long row_no; + int col_no= -1; + MYSQL_RES* UNINIT_VAR(res); + MYSQL* mysql= cur_con->mysql; + + static DYNAMIC_STRING ds_query; + static DYNAMIC_STRING ds_col; + static DYNAMIC_STRING ds_row; + const struct command_arg query_get_value_args[] = { + {"query", ARG_STRING, TRUE, &ds_query, "Query to run"}, + {"column name", ARG_STRING, TRUE, &ds_col, "Name of column"}, + {"row number", ARG_STRING, TRUE, &ds_row, "Number for row"} + }; + + DBUG_ENTER("var_set_query_get_value"); + + if (!mysql) + { + handle_no_active_connection(command, cur_con, &ds_res); + DBUG_VOID_RETURN; + } + + strip_parentheses(command); + DBUG_PRINT("info", ("query: %s", command->query)); + check_command_args(command, command->first_argument, query_get_value_args, + sizeof(query_get_value_args)/sizeof(struct command_arg), + ','); + + DBUG_PRINT("info", ("query: %s", ds_query.str)); + DBUG_PRINT("info", ("col: %s", ds_col.str)); + + /* Convert row number to int */ + if (!str2int(ds_row.str, 10, (long) 0, (long) INT_MAX, &row_no)) + die("Invalid row number: '%s'", ds_row.str); + DBUG_PRINT("info", ("row: %s, row_no: %ld", ds_row.str, row_no)); + dynstr_free(&ds_row); + + /* Remove any surrounding "'s from the query - if there is any */ + if (strip_surrounding(ds_query.str, '"', '"')) + die("Mismatched \"'s around query '%s'", ds_query.str); + + /* Run the query */ + if (mysql_real_query(mysql, ds_query.str, (ulong)ds_query.length)) + { + handle_error(curr_command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), &ds_res); + /* If error was acceptable, return empty string */ + dynstr_free(&ds_query); + dynstr_free(&ds_col); + eval_expr(var, "", 0); + DBUG_VOID_RETURN; + } + + if (!(res= mysql_store_result(mysql))) + { + report_or_die("Query '%s' didn't return a result set", ds_query.str); + dynstr_free(&ds_query); + dynstr_free(&ds_col); + eval_expr(var, "", 0); + DBUG_VOID_RETURN; + } + + { + /* Find column number from the given column name */ + uint i; + uint num_fields= mysql_num_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); + + for (i= 0; i < num_fields; i++) + { + if (strcmp(fields[i].name, ds_col.str) == 0 && + strlen(fields[i].name) == ds_col.length) + { + col_no= i; + break; + } + } + if (col_no == -1) + { + mysql_free_result(res); + report_or_die("Could not find column '%s' in the result of '%s'", + ds_col.str, ds_query.str); + dynstr_free(&ds_query); + dynstr_free(&ds_col); + DBUG_VOID_RETURN; + } + DBUG_PRINT("info", ("Found column %d with name '%s'", + i, fields[i].name)); + } + dynstr_free(&ds_col); + + { + /* Get the value */ + MYSQL_ROW row; + long rows= 0; + const char* value= "No such row"; + + while ((row= mysql_fetch_row(res))) + { + if (++rows == row_no) + { + + DBUG_PRINT("info", ("At row %ld, column %d is '%s'", + row_no, col_no, row[col_no])); + /* Found the row to get */ + if (row[col_no]) + value= row[col_no]; + else + value= "NULL"; + + break; + } + } + eval_expr(var, value, 0, false, false); + } + dynstr_free(&ds_query); + mysql_free_result(res); + + DBUG_VOID_RETURN; +} + + +void var_copy(VAR *dest, VAR *src) +{ + dest->int_val= src->int_val; + dest->is_int= src->is_int; + dest->int_dirty= src->int_dirty; + + /* Alloc/realloc data for str_val in dest */ + if (dest->alloced_len < src->alloced_len && + !(dest->str_val= dest->str_val + ? (char*)my_realloc(PSI_NOT_INSTRUMENTED, dest->str_val, src->alloced_len, + MYF(MY_WME|MY_FAE)) + : (char*)my_malloc(PSI_NOT_INSTRUMENTED, src->alloced_len, + MYF(MY_WME|MY_FAE)))) + die("Out of memory"); + else + dest->alloced_len= src->alloced_len; + + /* Copy str_val data to dest */ + dest->str_val_len= src->str_val_len; + if (src->str_val_len) + memcpy(dest->str_val, src->str_val, src->str_val_len); +} + + +void eval_expr(VAR *v, const char *p, const char **p_end, + bool open_end, bool do_eval) +{ + + DBUG_ENTER("eval_expr"); + DBUG_PRINT("enter", ("p: '%s'", p)); + + /* Skip to treat as pure string if no evaluation */ + if (! do_eval) + goto NO_EVAL; + + if (*p == '$') + { + VAR *vp; + const char* expected_end= *p_end; // Remember var end + if ((vp= var_get(p, p_end, 0, 0))) + var_copy(v, vp); + + /* Apparently it is not safe to assume null-terminated string */ + v->str_val[v->str_val_len]= 0; + + /* Make sure there was just a $variable and nothing else */ + const char* end= *p_end + 1; + if (end < expected_end && !open_end) + die("Found junk '%.*b' after $variable in expression", + (int)(expected_end - end - 1), end); + + DBUG_VOID_RETURN; + } + + if (*p == '`') + { + var_query_set(v, p, p_end); + DBUG_VOID_RETURN; + } + + { + /* Check if this is a "let $var= query_get_value()" */ + const char* get_value_str= "query_get_value"; + const size_t len= strlen(get_value_str); + if (strncmp(p, get_value_str, len)==0) + { + struct st_command command; + memset(&command, 0, sizeof(command)); + command.query= (char*)p; + command.first_word_len= (int)len; + command.first_argument= command.query + len; + command.end= (char*)*p_end; + command.abort_on_error= 1; /* avoid uninitialized variables */ + var_set_query_get_value(&command, v); + DBUG_VOID_RETURN; + } + } + + NO_EVAL: + { + size_t new_val_len = (p_end && *p_end) ? + (size_t)(*p_end - p) : strlen(p); + if (new_val_len + 1 >= v->alloced_len) + { + static size_t MIN_VAR_ALLOC= 32; + v->alloced_len = (new_val_len < MIN_VAR_ALLOC - 1) ? + MIN_VAR_ALLOC : new_val_len + 1; + if (!(v->str_val = + v->str_val ? + (char*)my_realloc(PSI_NOT_INSTRUMENTED, v->str_val, v->alloced_len+1, + MYF(MY_WME|MY_FAE)) : + (char*)my_malloc(PSI_NOT_INSTRUMENTED, v->alloced_len+1, + MYF(MY_WME|MY_FAE)))) + die("Out of memory"); + } + v->str_val_len = new_val_len; + memcpy(v->str_val, p, new_val_len); + v->str_val[new_val_len] = 0; + var_check_int(v); + } + DBUG_VOID_RETURN; +} + + +bool open_and_set_current(const char *name) +{ + FILE *opened= fopen(name, "rb"); + + if (!opened) + return false; + + cur_file++; + cur_file->file= opened; + cur_file->file_name= my_strdup(PSI_NOT_INSTRUMENTED, name, MYF(MY_FAE)); + cur_file->lineno=1; + return true; +} + + +void open_file(const char *name) +{ + char buff[FN_REFLEN]; + size_t length; + char *curname= cur_file->file_name; + DBUG_ENTER("open_file"); + DBUG_PRINT("enter", ("name: %s", name)); + + if (cur_file == file_stack_end) + die("Source directives are nesting too deep"); + + if (test_if_hard_path(name)) + { + if (open_and_set_current(name)) + DBUG_VOID_RETURN; + } + else + { + /* + if overlay-dir is specified, and the file is located somewhere + under overlay-dir or under suite-dir, the search works as follows: + + 0.let suffix be current file dirname relative to suite-dir or overlay-dir + 1.try in overlay-dir/suffix + 2.try in suite-dir/suffix + 3.try in overlay-dir + 4.try in suite-dir + 5.try in basedir + + consider an example: 'rty' overlay of the 'qwe' suite, + file qwe/include/some.inc contains the line + --source thing.inc + we look for it in this order: + 0.suffix is "include/" + 1.try in rty/include/thing.inc + 2.try in qwe/include/thing.inc + 3.try in try/thing.inc | this is useful when t/a.test has + 4.try in qwe/thing.inc | source include/b.inc; + 5.try in mysql-test/include/thing.inc + + otherwise the search is as follows + 1.try in current file dirname + 3.try in overlay-dir (if any) + 4.try in suite-dir + 5.try in basedir + */ + + fix_win_paths(curname, sizeof(curname)); + + bool in_overlay= opt_overlay_dir && + !strncmp(curname, opt_overlay_dir, overlay_dir_len); + bool in_suiteir= opt_overlay_dir && !in_overlay && + !strncmp(curname, opt_suite_dir, suite_dir_len); + if (in_overlay || in_suiteir) + { + size_t prefix_len = in_overlay ? overlay_dir_len : suite_dir_len; + char buf2[FN_REFLEN], *suffix= buf2 + prefix_len; + dirname_part(buf2, curname, &length); + + /* 1. first we look in the overlay dir */ + strxnmov(buff, sizeof(buff), opt_overlay_dir, suffix, name, NullS); + + /* + Overlaid rty/include/thing.inc can contain the line + --source thing.inc + which would mean to include qwe/include/thing.inc. + But it looks like including "itself", so don't try to open the file, + if buff contains the same file name as curname. + */ + if (strcmp(buff, curname) && open_and_set_current(buff)) + DBUG_VOID_RETURN; + + /* 2. if that failed, we look in the suite dir */ + strxnmov(buff, sizeof(buff), opt_suite_dir, suffix, name, NullS); + + /* buff can not be equal to curname, as a file can never include itself */ + if (open_and_set_current(buff)) + DBUG_VOID_RETURN; + } + else + { + /* 1. try in current file dirname */ + dirname_part(buff, curname, &length); + strxnmov(buff, sizeof(buff), buff, name, NullS); + if (open_and_set_current(buff)) + DBUG_VOID_RETURN; + } + + /* 3. now, look in the overlay dir */ + if (opt_overlay_dir) + { + strxmov(buff, opt_overlay_dir, name, NullS); + if (open_and_set_current(buff)) + DBUG_VOID_RETURN; + } + + /* 4. if that failed - look in the suite dir */ + strxmov(buff, opt_suite_dir, name, NullS); + if (open_and_set_current(buff)) + DBUG_VOID_RETURN; + + /* 5. the last resort - look in the base dir */ + strxnmov(buff, sizeof(buff), opt_basedir, name, NullS); + if (open_and_set_current(buff)) + DBUG_VOID_RETURN; + } + + die("Could not open '%s' for reading, errno: %d", name, errno); + DBUG_VOID_RETURN; +} + + +/* + Source and execute the given file + + SYNOPSIS + do_source() + query called command + + DESCRIPTION + source <file_name> + + Open the file <file_name> and execute it + +*/ + +void do_source(struct st_command *command) +{ + static DYNAMIC_STRING ds_filename; + const struct command_arg source_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to source" } + }; + DBUG_ENTER("do_source"); + + check_command_args(command, command->first_argument, source_args, + sizeof(source_args)/sizeof(struct command_arg), + ' '); + + /* + If this file has already been sourced, don't source it again. + It's already available in the q_lines cache. + */ + if (parser.current_line < (parser.read_lines - 1)) + ; /* Do nothing */ + else + { + DBUG_PRINT("info", ("sourcing file: %s", ds_filename.str)); + open_file(ds_filename.str); + } + + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +static void init_builtin_echo(void) +{ +#ifdef _WIN32 + size_t echo_length; + + /* Look for "echo.exe" in same dir as mysqltest was started from */ + dirname_part(builtin_echo, my_progname, &echo_length); + fn_format(builtin_echo, ".\\echo.exe", + builtin_echo, "", MYF(MY_REPLACE_DIR)); + + /* Make sure echo.exe exists */ + if (access(builtin_echo, F_OK) != 0) + builtin_echo[0]= 0; + return; + +#else + + builtin_echo[0]= 0; + return; + +#endif +} + + +/* + 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, size_t search_len, + const char *replace_str, size_t replace_len) +{ + DYNAMIC_STRING ds_tmp; + const char *start= strstr(ds_str->str, search_str); + if (!start) + return 1; + init_dynamic_string(&ds_tmp, "", + ds_str->length + replace_len, 256); + dynstr_append_mem(&ds_tmp, ds_str->str, start - ds_str->str); + dynstr_append_mem(&ds_tmp, replace_str, replace_len); + dynstr_append(&ds_tmp, start + search_len); + dynstr_set(ds_str, ds_tmp.str); + dynstr_free(&ds_tmp); + return 0; +} + +#ifdef _WIN32 +/** + Check if background execution of command was requested. + Like in Unix shell, we assume background execution of the last + character in command is a ampersand (we do not tokenize though) +*/ +static bool is_background_command(const DYNAMIC_STRING *ds) +{ + for (size_t i= ds->length - 1; i > 1; i--) + { + char c= ds->str[i]; + if (!isspace(c)) + return (c == '&'); + } + return false; +} + +/** + Execute OS command in background. We assume that the last character + is ampersand, i.e is_background_command() returned +*/ +#include <string> +static int execute_in_background(char *cmd) +{ + STARTUPINFO s{}; + PROCESS_INFORMATION pi{}; + char *end= strrchr(cmd, '&'); + DBUG_ASSERT(end); + *end =0; + std::string scmd("cmd /c "); + scmd.append(cmd); + BOOL ok= + CreateProcess(0, (char *)scmd.c_str(), 0, 0, 0, CREATE_NO_WINDOW, 0, 0, &s, &pi); + *end= '&'; + if (!ok) + return (int) GetLastError(); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return 0; +} +#endif + +/* + Execute given command. + + SYNOPSIS + do_exec() + query called command + + DESCRIPTION + exec <command> + + Execute the text between exec and end of line in a subprocess. + The error code returned from the subprocess is checked against the + expected error array, previously set with the --error command. + It can thus be used to execute a command that shall fail. + + NOTE + Although mysqltest is executed from cygwin shell, the command will be + executed in "cmd.exe". Thus commands like "rm" etc can NOT be used, use + mysqltest command(s) like "remove_file" for that +*/ + +void do_exec(struct st_command *command) +{ + int error; + char buf[512]; + FILE *res_file; + char *cmd= command->first_argument; + DYNAMIC_STRING ds_cmd; + DYNAMIC_STRING ds_sorted, *ds_result; + DBUG_ENTER("do_exec"); + DBUG_PRINT("enter", ("cmd: '%s'", cmd)); + + var_set_int("$sys_errno",0); + + /* Skip leading space */ + while (*cmd && my_isspace(charset_info, *cmd)) + cmd++; + if (!*cmd) + { + report_or_die("Missing argument in exec"); + DBUG_VOID_RETURN; + } + command->last_argument= command->end; + + init_dynamic_string(&ds_cmd, 0, command->query_len+256, 256); + /* Eval the command, thus replacing all environment variables */ + do_eval(&ds_cmd, cmd, command->end, !is_windows); + + /* Check if echo should be replaced with "builtin" echo */ + if (builtin_echo[0] && strncmp(cmd, "echo", 4) == 0) + { + /* Replace echo with our "builtin" echo */ + replace(&ds_cmd, "echo", 4, builtin_echo, strlen(builtin_echo)); + } + +#ifdef _WIN32 + /* Replace /dev/null with NUL */ + while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) + ; + /* Replace "closed stdout" with non existing output fd */ + while(replace(&ds_cmd, ">&-", 3, ">&4", 3) == 0) + ; +#endif + + if (disable_result_log) + { + /* Collect stderr output as well, for the case app. crashes or returns error.*/ + dynstr_append(&ds_cmd, " 2>&1"); + } + + DBUG_PRINT("info", ("Executing '%s' as '%s'", + command->first_argument, ds_cmd.str)); + +#ifdef _WIN32 + if (is_background_command(&ds_cmd)) + { + error= execute_in_background(ds_cmd.str); + goto end; + } +#endif + + if (!(res_file= my_popen(ds_cmd.str, "r"))) + { + dynstr_free(&ds_cmd); + if (command->abort_on_error) + report_or_die("popen(\"%s\", \"r\") failed", command->first_argument); + DBUG_VOID_RETURN; + } + + ds_result= &ds_res; + if (display_result_sorted) + { + init_dynamic_string(&ds_sorted, "", 1024, 1024); + ds_result= &ds_sorted; + } + int len; + while (my_fgets(buf, sizeof(buf), res_file,&len)) + { + replace_dynstr_append_mem(ds_result, buf, len); + } + error= my_pclose(res_file); + + if (display_result_sorted) + { + dynstr_append_sorted(&ds_res, &ds_sorted, 0); + dynstr_free(&ds_sorted); + } +#ifdef _WIN32 +end: +#endif + if (error) + { + uint status= WEXITSTATUS(error); + int i; + + if (command->abort_on_error) + { + report_or_die("exec of '%s' failed, error: %d, status: %d, errno: %d\n" + "Output from before failure:\n%s\n", + ds_cmd.str, error, status, errno, + ds_res.str); + dynstr_free(&ds_cmd); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", + ("error: %d, status: %d", error, status)); + + i= match_expected_error(command, status, NULL); + + if (i >= 0) + DBUG_PRINT("info", ("command \"%s\" failed with expected error: %d", + command->first_argument, status)); + else + { + dynstr_free(&ds_cmd); + if (command->expected_errors.count > 0) + report_or_die("command \"%s\" failed with wrong error: %d", + command->first_argument, status); + } + var_set_int("$sys_errno",status); + } + else if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + log_msg("exec of '%s failed, error: %d, errno: %d", + ds_cmd.str, error, errno); + dynstr_free(&ds_cmd); + report_or_die("command \"%s\" succeeded - should have failed with " + "errno %d...", + command->first_argument, + command->expected_errors.err[0].code.errnum); + } + + dynstr_free(&ds_cmd); + + if (disable_result_log) + { + /* Disable output in case of successful exit.*/ + dynstr_set(&ds_res,""); + } + DBUG_VOID_RETURN; +} + +enum enum_operator +{ + DO_DEC, + DO_INC +}; + + +/* + Decrease or increase the value of a variable + + SYNOPSIS + do_modify_var() + query called command + op operation to perform on the var + + DESCRIPTION + dec $var_name + inc $var_name + +*/ + +int do_modify_var(struct st_command *command, + enum enum_operator op) +{ + const char *p= command->first_argument; + VAR* v; + if (!*p) + die("Missing argument to %.*b", command->first_word_len, + command->query); + if (*p != '$') + die("The argument to %.*b must be a variable (start with $)", + command->first_word_len, command->query); + v= var_get(p, &p, 1, 0); + if (! v->is_int) + die("Cannot perform inc/dec on a non-numeric value"); + switch (op) { + case DO_DEC: + v->int_val--; + break; + case DO_INC: + v->int_val++; + break; + default: + die("Invalid operator to do_modify_var"); + break; + } + v->int_dirty= true; + command->last_argument= (char*)++p; + return 0; +} + + +/* + Wrapper for 'system' function + + NOTE + If mysqltest is executed from cygwin shell, the command will be + executed in the "windows command interpreter" cmd.exe and we prepend "sh" + to make it be executed by cygwins "bash". Thus commands like "rm", + "mkdir" as well as shellscripts can executed by "system" in Windows. + +*/ + +int my_system(DYNAMIC_STRING* ds_cmd) +{ + return system(ds_cmd->str); +} + + +/* + SYNOPSIS + do_system + command called command + + DESCRIPTION + system <command> + + Eval the query to expand any $variables in the command. + Execute the command with the "system" command. + +*/ + +void do_system(struct st_command *command) +{ + DYNAMIC_STRING ds_cmd; + DBUG_ENTER("do_system"); + + if (strlen(command->first_argument) == 0) + { + report_or_die("Missing arguments to system, nothing to do!"); + DBUG_VOID_RETURN; + } + + init_dynamic_string(&ds_cmd, 0, command->query_len + 64, 256); + + /* Eval the system command, thus replacing all environment variables */ + do_eval(&ds_cmd, command->first_argument, command->end, !is_windows); + +#ifdef _WIN32 + /* Replace /dev/null with NUL */ + while(replace(&ds_cmd, "/dev/null", 9, "NUL", 3) == 0) + ; +#endif + + + DBUG_PRINT("info", ("running system command '%s' as '%s'", + command->first_argument, ds_cmd.str)); + if (my_system(&ds_cmd)) + { + if (command->abort_on_error) + report_or_die("system command '%s' failed", command->first_argument); + else + { + /* If ! abort_on_error, log message and continue */ + dynstr_append(&ds_res, "system command '"); + replace_dynstr_append(&ds_res, command->first_argument); + dynstr_append(&ds_res, "' failed\n"); + } + } + + command->last_argument= command->end; + dynstr_free(&ds_cmd); + DBUG_VOID_RETURN; +} + + +/* returns TRUE if path is inside a sandbox */ +bool is_sub_path(const char *path, size_t plen, const char *sandbox) +{ + size_t len; + if (!sandbox) + return false; + len= strlen(sandbox); + if (plen <= len || memcmp(path, sandbox, len-1) || path[len] != '/') + return false; + return true; +} + + +/* returns TRUE if path cannot be modified */ +bool bad_path(const char *path) +{ + size_t plen= strlen(path); + + const char *vardir= getenv("MYSQLTEST_VARDIR"); + if (is_sub_path(path, plen, vardir)) + return false; + + const char *tmpdir= getenv("MYSQL_TMP_DIR"); + if (is_sub_path(path, plen, tmpdir)) + return false; + + report_or_die("Path '%s' is not a subdirectory of MYSQLTEST_VARDIR '%s'" + "or MYSQL_TMP_DIR '%s'", + path, vardir, tmpdir); + return true; +} + + +/* + SYNOPSIS + set_wild_chars + set true to set * etc. as wild char, false to reset + + DESCRIPTION + Auxiliary function to set "our" wild chars before calling wild_compare + This is needed because the default values are changed to SQL syntax + in mysqltest_embedded. +*/ + +void set_wild_chars (my_bool set) +{ + static char old_many= 0, old_one, old_prefix; + + if (set) + { + if (wild_many == '*') return; // No need + old_many= wild_many; + old_one= wild_one; + old_prefix= wild_prefix; + wild_many= '*'; + wild_one= '?'; + wild_prefix= 0; + } + else + { + if (! old_many) return; // Was not set + wild_many= old_many; + wild_one= old_one; + wild_prefix= old_prefix; + } +} + + +/* + SYNOPSIS + do_remove_file + command called command + + DESCRIPTION + remove_file <file_name> + Remove the file <file_name> +*/ + +void do_remove_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_filename; + const struct command_arg rm_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to delete" } + }; + DBUG_ENTER("do_remove_file"); + + check_command_args(command, command->first_argument, + rm_args, sizeof(rm_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_filename.str)) + DBUG_VOID_RETURN; + + DBUG_PRINT("info", ("removing file: %s", ds_filename.str)); + error= my_delete(ds_filename.str, MYF(disable_warnings ? 0 : MY_WME)) != 0; + handle_command_error(command, error, my_errno); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_remove_files_wildcard + command called command + + DESCRIPTION + remove_files_wildcard <directory> [<file_name_pattern>] + Remove the files in <directory> optionally matching <file_name_pattern> +*/ + +void do_remove_files_wildcard(struct st_command *command) +{ + int error= 0, sys_errno= 0; + size_t i, directory_length; + MY_DIR *dir_info; + FILEINFO *file; + char dir_separator[2]; + static DYNAMIC_STRING ds_directory; + static DYNAMIC_STRING ds_wild; + static DYNAMIC_STRING ds_file_to_remove; + char dirname[FN_REFLEN]; + + const struct command_arg rm_args[] = { + { "directory", ARG_STRING, TRUE, &ds_directory, + "Directory containing files to delete" }, + { "filename", ARG_STRING, FALSE, &ds_wild, "File pattern to delete" } + }; + DBUG_ENTER("do_remove_files_wildcard"); + + check_command_args(command, command->first_argument, + rm_args, sizeof(rm_args)/sizeof(struct command_arg), + ' '); + fn_format(dirname, ds_directory.str, "", "", MY_UNPACK_FILENAME); + + if (bad_path(ds_directory.str)) + DBUG_VOID_RETURN; + + DBUG_PRINT("info", ("listing directory: %s", dirname)); + if (!(dir_info= my_dir(dirname, MYF(MY_DONT_SORT | MY_WANT_STAT | MY_WME)))) + { + error= 1; + sys_errno= my_errno; + goto end; + } + init_dynamic_string(&ds_file_to_remove, dirname, 1024, 1024); + dir_separator[0]= FN_LIBCHAR; + dynstr_append_mem(&ds_file_to_remove, dir_separator, 1); + directory_length= ds_file_to_remove.length; + + /* Set default wild chars for wild_compare, is changed in embedded mode */ + set_wild_chars(1); + + for (i= 0; i < dir_info->number_of_files; i++) + { + file= dir_info->dir_entry + i; + /* Remove only regular files, i.e. no directories etc. */ + /* if (!MY_S_ISREG(file->mystat->st_mode)) */ + /* MY_S_ISREG does not work here on Windows, just skip directories */ + if (MY_S_ISDIR(file->mystat->st_mode)) + continue; + if (ds_wild.length && + wild_compare(file->name, ds_wild.str, 0)) + continue; + ds_file_to_remove.length= directory_length; + dynstr_append(&ds_file_to_remove, file->name); + DBUG_PRINT("info", ("removing file: %s", ds_file_to_remove.str)); + if ((error= (my_delete(ds_file_to_remove.str, MYF(MY_WME)) != 0))) + sys_errno= my_errno; + if (error) + break; + } + set_wild_chars(0); + my_dirend(dir_info); + +end: + handle_command_error(command, error, sys_errno); + dynstr_free(&ds_directory); + dynstr_free(&ds_wild); + dynstr_free(&ds_file_to_remove); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_copy_file + command command handle + + DESCRIPTION + copy_file <from_file> <to_file> + Copy <from_file> to <to_file> + + NOTE! Will fail if <to_file> exists +*/ + +void do_copy_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_from_file; + static DYNAMIC_STRING ds_to_file; + const struct command_arg copy_file_args[] = { + { "from_file", ARG_STRING, TRUE, &ds_from_file, "Filename to copy from" }, + { "to_file", ARG_STRING, TRUE, &ds_to_file, "Filename to copy to" } + }; + DBUG_ENTER("do_copy_file"); + + check_command_args(command, command->first_argument, + copy_file_args, + sizeof(copy_file_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_to_file.str)) + DBUG_VOID_RETURN; + + DBUG_PRINT("info", ("Copy %s to %s", ds_from_file.str, ds_to_file.str)); + /* MY_HOLD_ORIGINAL_MODES prevents attempts to chown the file */ + error= (my_copy(ds_from_file.str, ds_to_file.str, + MYF(MY_DONT_OVERWRITE_FILE | MY_WME | MY_HOLD_ORIGINAL_MODES)) != 0); + handle_command_error(command, error, my_errno); + dynstr_free(&ds_from_file); + dynstr_free(&ds_to_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_move_file + command command handle + + DESCRIPTION + move_file <from_file> <to_file> + Move <from_file> to <to_file> +*/ + +void do_move_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_from_file; + static DYNAMIC_STRING ds_to_file; + const struct command_arg move_file_args[] = { + { "from_file", ARG_STRING, TRUE, &ds_from_file, "Filename to move from" }, + { "to_file", ARG_STRING, TRUE, &ds_to_file, "Filename to move to" } + }; + DBUG_ENTER("do_move_file"); + + check_command_args(command, command->first_argument, + move_file_args, + sizeof(move_file_args)/sizeof(struct command_arg), + ' '); + + size_t from_plen = strlen(ds_from_file.str); + size_t to_plen = strlen(ds_to_file.str); + const char *vardir= getenv("MYSQLTEST_VARDIR"); + const char *tmpdir= getenv("MYSQL_TMP_DIR"); + + if (!((is_sub_path(ds_from_file.str, from_plen, vardir) && + is_sub_path(ds_to_file.str, to_plen, vardir)) || + (is_sub_path(ds_from_file.str, from_plen, tmpdir) && + is_sub_path(ds_to_file.str, to_plen, tmpdir)))) { + report_or_die("Paths '%s' and '%s' are not both under MYSQLTEST_VARDIR '%s'" + "or both under MYSQL_TMP_DIR '%s'", + ds_from_file, ds_to_file, vardir, tmpdir); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", ("Move %s to %s", ds_from_file.str, ds_to_file.str)); + error= (my_rename(ds_from_file.str, ds_to_file.str, + MYF(disable_warnings ? 0 : MY_WME)) != 0); + handle_command_error(command, error, my_errno); + dynstr_free(&ds_from_file); + dynstr_free(&ds_to_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_chmod_file + command command handle + + DESCRIPTION + chmod <octal> <file_name> + Change file permission of <file_name> + +*/ + +void do_chmod_file(struct st_command *command) +{ + long mode= 0; + int err_code; + static DYNAMIC_STRING ds_mode; + static DYNAMIC_STRING ds_file; + const struct command_arg chmod_file_args[] = { + { "mode", ARG_STRING, TRUE, &ds_mode, "Mode of file(octal) ex. 0660"}, + { "filename", ARG_STRING, TRUE, &ds_file, "Filename of file to modify" } + }; + DBUG_ENTER("do_chmod_file"); + + check_command_args(command, command->first_argument, + chmod_file_args, + sizeof(chmod_file_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_file.str)) + DBUG_VOID_RETURN; + + /* Parse what mode to set */ + if (ds_mode.length != 4 || + str2int(ds_mode.str, 8, 0, INT_MAX, &mode) == NullS) + die("You must write a 4 digit octal number for mode"); + + DBUG_PRINT("info", ("chmod %o %s", (uint)mode, ds_file.str)); + err_code= chmod(ds_file.str, mode); + if (err_code < 0) + err_code= 1; + handle_command_error(command, err_code, errno); + dynstr_free(&ds_mode); + dynstr_free(&ds_file); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_file_exists + command called command + + DESCRIPTION + fiile_exist <file_name> + Check if file <file_name> exists +*/ + +void do_file_exist(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_filename; + const struct command_arg file_exist_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to check if it exist" } + }; + DBUG_ENTER("do_file_exist"); + + check_command_args(command, command->first_argument, + file_exist_args, + sizeof(file_exist_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("Checking for existence of file: %s", ds_filename.str)); + error= (access(ds_filename.str, F_OK) != 0); + handle_command_error(command, error, errno); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_mkdir + command called command + + DESCRIPTION + mkdir <dir_name> + Create the directory <dir_name> +*/ + +void do_mkdir(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_dirname; + const struct command_arg mkdir_args[] = { + {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to create"} + }; + DBUG_ENTER("do_mkdir"); + + check_command_args(command, command->first_argument, + mkdir_args, sizeof(mkdir_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_dirname.str)) + DBUG_VOID_RETURN; + + DBUG_PRINT("info", ("creating directory: %s", ds_dirname.str)); + error= my_mkdir(ds_dirname.str, 0777, MYF(MY_WME)) != 0; + handle_command_error(command, error, my_errno); + dynstr_free(&ds_dirname); + DBUG_VOID_RETURN; +} + + + +/* + SYNOPSIS + do_rmdir + command called command + + DESCRIPTION + rmdir <dir_name> + Remove the directory tree +*/ + +void do_rmdir(struct st_command *command) +{ + static DYNAMIC_STRING ds_dirname; + const struct command_arg rmdir_args[] = { + { "dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to remove" } + }; + DBUG_ENTER("do_rmdir"); + + check_command_args(command, command->first_argument, + rmdir_args, sizeof(rmdir_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_dirname.str)) + DBUG_VOID_RETURN; + + DBUG_PRINT("info", ("removing directory: %s", ds_dirname.str)); + if (my_rmtree(ds_dirname.str, MYF(0))) + handle_command_error(command, 1, errno); + + dynstr_free(&ds_dirname); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + get_list_files + ds output + ds_dirname dir to list + ds_wild wild-card file pattern (can be empty) + + DESCRIPTION + list all entries in directory (matching ds_wild if given) +*/ + +static int get_list_files(DYNAMIC_STRING *ds, const DYNAMIC_STRING *ds_dirname, + const DYNAMIC_STRING *ds_wild) +{ + size_t i; + MY_DIR *dir_info; + FILEINFO *file; + DBUG_ENTER("get_list_files"); + + DBUG_PRINT("info", ("listing directory: %s", ds_dirname->str)); + if (!(dir_info= my_dir(ds_dirname->str, MYF(MY_WANT_SORT)))) + DBUG_RETURN(1); + set_wild_chars(1); + for (i= 0; i < dir_info->number_of_files; i++) + { + file= dir_info->dir_entry + i; + if (ds_wild && ds_wild->length && + wild_compare(file->name, ds_wild->str, 0)) + continue; + replace_dynstr_append(ds, file->name); + dynstr_append(ds, "\n"); + } + set_wild_chars(0); + my_dirend(dir_info); + DBUG_RETURN(0); +} + + +/* + SYNOPSIS + do_list_files + command called command + + DESCRIPTION + list_files <dir_name> [<file_name>] + List files and directories in directory <dir_name> (like `ls`) + [Matching <file_name>, where wild-cards are allowed] +*/ + +static void do_list_files(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_dirname; + static DYNAMIC_STRING ds_wild; + const struct command_arg list_files_args[] = { + {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to list"}, + {"file", ARG_STRING, FALSE, &ds_wild, "Filename (incl. wildcard)"} + }; + DBUG_ENTER("do_list_files"); + command->used_replace= 1; + + check_command_args(command, command->first_argument, + list_files_args, + sizeof(list_files_args)/sizeof(struct command_arg), ' '); + + error= get_list_files(&ds_res, &ds_dirname, &ds_wild); + handle_command_error(command, error, my_errno); + dynstr_free(&ds_dirname); + dynstr_free(&ds_wild); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_list_files_write_file_command + command called command + append append file, or create new + + DESCRIPTION + list_files_{write|append}_file <filename> <dir_name> [<match_file>] + List files and directories in directory <dir_name> (like `ls`) + [Matching <match_file>, where wild-cards are allowed] + + Note: File will be truncated if exists and append is not true. +*/ + +static void do_list_files_write_file_command(struct st_command *command, + my_bool append) +{ + int error; + static DYNAMIC_STRING ds_content; + static DYNAMIC_STRING ds_filename; + static DYNAMIC_STRING ds_dirname; + static DYNAMIC_STRING ds_wild; + const struct command_arg list_files_args[] = { + {"filename", ARG_STRING, TRUE, &ds_filename, "Filename for write"}, + {"dirname", ARG_STRING, TRUE, &ds_dirname, "Directory to list"}, + {"file", ARG_STRING, FALSE, &ds_wild, "Filename (incl. wildcard)"} + }; + DBUG_ENTER("do_list_files_write_file"); + command->used_replace= 1; + + check_command_args(command, command->first_argument, + list_files_args, + sizeof(list_files_args)/sizeof(struct command_arg), ' '); + + if (bad_path(ds_filename.str)) + DBUG_VOID_RETURN; + + init_dynamic_string(&ds_content, "", 1024, 1024); + error= get_list_files(&ds_content, &ds_dirname, &ds_wild); + handle_command_error(command, error, my_errno); + str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); + dynstr_free(&ds_content); + dynstr_free(&ds_filename); + dynstr_free(&ds_dirname); + dynstr_free(&ds_wild); + DBUG_VOID_RETURN; +} + + +/* + Read characters from line buffer or file. This is needed to allow + my_ungetc() to buffer MAX_DELIMITER_LENGTH characters for a file + + NOTE: + This works as long as one doesn't change files (with 'source file_name') + when there is things pushed into the buffer. This should however not + happen for any tests in the test suite. +*/ + +int my_getc(FILE *file) +{ + if (line_buffer_pos == line_buffer) + return fgetc(file); + return *--line_buffer_pos; +} + + +void my_ungetc(int c) +{ + *line_buffer_pos++= (char) c; +} + + +void read_until_delimiter(DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_delimiter) +{ + char c; + DBUG_ENTER("read_until_delimiter"); + DBUG_PRINT("enter", ("delimiter: %s, length: %u", + ds_delimiter->str, (uint) ds_delimiter->length)); + + if (ds_delimiter->length > MAX_DELIMITER_LENGTH) + die("Max delimiter length(%d) exceeded", MAX_DELIMITER_LENGTH); + + /* Read from file until delimiter is found */ + while (1) + { + c= my_getc(cur_file->file); + if (c == '\r') + c= my_getc(cur_file->file); + if (c == '\n') + { + cur_file->lineno++; + + /* Skip newline from the same line as the command */ + if (start_lineno == (cur_file->lineno - 1)) + continue; + } + else if (start_lineno == cur_file->lineno) + { + /* + No characters except \n are allowed on + the same line as the command + */ + report_or_die("Trailing characters found after command"); + } + + if (feof(cur_file->file)) + report_or_die("End of file encountered before '%s' delimiter was found", + ds_delimiter->str); + + if (match_delimiter(c, ds_delimiter->str, ds_delimiter->length)) + { + DBUG_PRINT("exit", ("Found delimiter '%s'", ds_delimiter->str)); + break; + } + dynstr_append_mem(ds, (const char*)&c, 1); + } + DBUG_PRINT("exit", ("ds: %s", ds->str)); + DBUG_VOID_RETURN; +} + + +void do_write_file_command(struct st_command *command, my_bool append) +{ + static DYNAMIC_STRING ds_content; + static DYNAMIC_STRING ds_filename; + static DYNAMIC_STRING ds_delimiter; + const struct command_arg write_file_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to write to" }, + { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } + }; + DBUG_ENTER("do_write_file"); + + check_command_args(command, + command->first_argument, + write_file_args, + sizeof(write_file_args)/sizeof(struct command_arg), + ' '); + + if (bad_path(ds_filename.str)) + DBUG_VOID_RETURN; + + if (!append && access(ds_filename.str, F_OK) == 0) + { + /* The file should not be overwritten */ + die("File already exist: '%s'", ds_filename.str); + } + + ds_content= command->content; + /* If it hasn't been done already by a loop iteration, fill it in */ + if (! ds_content.str) + { + /* If no delimiter was provided, use EOF */ + if (ds_delimiter.length == 0) + dynstr_set(&ds_delimiter, "EOF"); + + init_dynamic_string(&ds_content, "", 1024, 1024); + read_until_delimiter(&ds_content, &ds_delimiter); + command->content= ds_content; + } + /* This function could be called even if "false", so check before printing */ + if (cur_block->ok) + { + DBUG_PRINT("info", ("Writing to file: %s", ds_filename.str)); + str_to_file2(ds_filename.str, ds_content.str, ds_content.length, append); + } + dynstr_free(&ds_filename); + dynstr_free(&ds_delimiter); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_write_file + command called command + + DESCRIPTION + write_file <file_name> [<delimiter>]; + <what to write line 1> + <...> + < what to write line n> + EOF + + --write_file <file_name>; + <what to write line 1> + <...> + < what to write line n> + EOF + + Write everything between the "write_file" command and 'delimiter' + to "file_name" + + NOTE! Will fail if <file_name> exists + + Default <delimiter> is EOF + +*/ + +void do_write_file(struct st_command *command) +{ + do_write_file_command(command, FALSE); +} + + +/* + SYNOPSIS + do_append_file + command called command + + DESCRIPTION + append_file <file_name> [<delimiter>]; + <what to write line 1> + <...> + < what to write line n> + EOF + + --append_file <file_name>; + <what to write line 1> + <...> + < what to write line n> + EOF + + Append everything between the "append_file" command + and 'delimiter' to "file_name" + + Default <delimiter> is EOF + +*/ + +void do_append_file(struct st_command *command) +{ + do_write_file_command(command, TRUE); +} + + +/* + SYNOPSIS + do_cat_file + command called command + + DESCRIPTION + cat_file <file_name>; + + Print the given file to result log + +*/ + +void do_cat_file(struct st_command *command) +{ + int error; + static DYNAMIC_STRING ds_filename; + const struct command_arg cat_file_args[] = { + { "filename", ARG_STRING, TRUE, &ds_filename, "File to read from" } + }; + DBUG_ENTER("do_cat_file"); + + check_command_args(command, + command->first_argument, + cat_file_args, + sizeof(cat_file_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("Reading from, file: %s", ds_filename.str)); + + error= cat_file(&ds_res, ds_filename.str); + handle_command_error(command, error, my_errno); + dynstr_free(&ds_filename); + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_diff_files + command called command + + DESCRIPTION + diff_files <file1> <file2>; + + Fails if the two files differ. + +*/ + +void do_diff_files(struct st_command *command) +{ + int error= 0; + static DYNAMIC_STRING ds_filename; + static DYNAMIC_STRING ds_filename2; + const struct command_arg diff_file_args[] = { + { "file1", ARG_STRING, TRUE, &ds_filename, "First file to diff" }, + { "file2", ARG_STRING, TRUE, &ds_filename2, "Second file to diff" } + }; + DBUG_ENTER("do_diff_files"); + + check_command_args(command, + command->first_argument, + diff_file_args, + sizeof(diff_file_args)/sizeof(struct command_arg), + ' '); + + if (access(ds_filename.str, F_OK) != 0) + die("command \"diff_files\" failed, file '%s' does not exist", + ds_filename.str); + + if (access(ds_filename2.str, F_OK) != 0) + die("command \"diff_files\" failed, file '%s' does not exist", + ds_filename2.str); + + if ((error= compare_files(ds_filename.str, ds_filename2.str)) && + match_expected_error(command, error, NULL) < 0) + { + /* + Compare of the two files failed, append them to output + so the failure can be analyzed, but only if it was not + expected to fail. + */ + show_diff(&ds_res, ds_filename.str, ds_filename2.str); + log_file.write(&ds_res); + log_file.flush(); + dynstr_set(&ds_res, 0); + } + + dynstr_free(&ds_filename); + dynstr_free(&ds_filename2); + handle_command_error(command, error, -1); + DBUG_VOID_RETURN; +} + + +struct st_connection * find_connection_by_name(const char *name) +{ + struct st_connection *con; + for (con= connections; con < next_con; con++) + { + if (!strcmp(con->name, name)) + { + return con; + } + } + return 0; /* Connection not found */ +} + + +/* + SYNOPSIS + do_send_quit + command called command + + DESCRIPTION + Sends a simple quit command to the server for the named connection. + +*/ + +void do_send_quit(struct st_command *command) +{ + char *p= command->first_argument, *name; + struct st_connection *con; + + DBUG_ENTER("do_send_quit"); + DBUG_PRINT("enter",("name: '%s'",p)); + + if (!*p) + die("Missing connection name in send_quit"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + + if (*p) + *p++= 0; + command->last_argument= p; + + if (!(con= find_connection_by_name(name))) + die("connection '%s' not found in connection pool", name); + + simple_command(con->mysql,COM_QUIT,0,0,1); + + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_change_user + command called command + + DESCRIPTION + change_user [<user>], [<passwd>], [<db>] + <user> - user to change to + <passwd> - user password + <db> - default database + + Changes the user and causes the database specified by db to become + the default (current) database for the the current connection. + +*/ + +void do_change_user(struct st_command *command) +{ + MYSQL *mysql = cur_con->mysql; + /* static keyword to make the NetWare compiler happy. */ + static DYNAMIC_STRING ds_user, ds_passwd, ds_db; + const struct command_arg change_user_args[] = { + { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, + { "password", ARG_STRING, FALSE, &ds_passwd, "Password used when connecting" }, + { "database", ARG_STRING, FALSE, &ds_db, "Database to select after connect" }, + }; + + DBUG_ENTER("do_change_user"); + + check_command_args(command, command->first_argument, + change_user_args, + sizeof(change_user_args)/sizeof(struct command_arg), + ','); + + if (cur_con->stmt) + { + mysql_stmt_close(cur_con->stmt); + cur_con->stmt= NULL; + } + + if (!ds_user.length) + { + dynstr_set(&ds_user, mysql->user); + + if (!ds_passwd.length) + dynstr_set(&ds_passwd, mysql->passwd); + + if (!ds_db.length) + dynstr_set(&ds_db, mysql->db); + } + + DBUG_PRINT("info",("connection: '%s' user: '%s' password: '%s' database: '%s'", + cur_con->name, ds_user.str, ds_passwd.str, ds_db.str)); + + if (mysql_change_user(mysql, ds_user.str, ds_passwd.str, ds_db.str)) + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), &ds_res); + else + handle_no_error(command); + + dynstr_free(&ds_user); + dynstr_free(&ds_passwd); + dynstr_free(&ds_db); + + DBUG_VOID_RETURN; +} + + +/* + SYNOPSIS + do_perl + command command handle + + DESCRIPTION + perl [<delimiter>]; + <perlscript line 1> + <...> + <perlscript line n> + EOF + + Execute everything after "perl" until <delimiter> as perl. + Useful for doing more advanced things + but still being able to execute it on all platforms. + + Default <delimiter> is EOF +*/ + +void do_perl(struct st_command *command) +{ + int error; + File fd; + FILE *res_file; + char buf[FN_REFLEN]; + char temp_file_path[FN_REFLEN]; + static DYNAMIC_STRING ds_script; + static DYNAMIC_STRING ds_delimiter; + const struct command_arg perl_args[] = { + { "delimiter", ARG_STRING, FALSE, &ds_delimiter, "Delimiter to read until" } + }; + DBUG_ENTER("do_perl"); + + check_command_args(command, + command->first_argument, + perl_args, + sizeof(perl_args)/sizeof(struct command_arg), + ' '); + + ds_script= command->content; + /* If it hasn't been done already by a loop iteration, fill it in */ + if (! ds_script.str) + { + /* If no delimiter was provided, use EOF */ + if (ds_delimiter.length == 0) + dynstr_set(&ds_delimiter, "EOF"); + + init_dynamic_string(&ds_script, "", 1024, 1024); + read_until_delimiter(&ds_script, &ds_delimiter); + command->content= ds_script; + } + + /* This function could be called even if "false", so check before doing */ + if (cur_block->ok) + { + DBUG_PRINT("info", ("Executing perl: %s", ds_script.str)); + + /* Create temporary file name */ + if ((fd= create_temp_file(temp_file_path, getenv("MYSQLTEST_VARDIR"), + "tmp", O_SHARE, MYF(MY_WME))) < 0) + die("Failed to create temporary file for perl command"); + my_close(fd, MYF(0)); + + str_to_file(temp_file_path, ds_script.str, ds_script.length); + + /* Use the same perl executable as the one that runs mysql-test-run.pl */ + const char *mtr_perl=getenv("MTR_PERL"); + if (!mtr_perl) + mtr_perl="perl"; + + /* Format the "perl <filename>" command */ + if (strchr(mtr_perl, ' ')) + my_snprintf(buf, sizeof(buf), "\"%s\" %s", mtr_perl, temp_file_path); + else + my_snprintf(buf, sizeof(buf), "%s %s", mtr_perl, temp_file_path); + + if (!(res_file= my_popen(buf, "r"))) + { + if (command->abort_on_error) + die("popen(\"%s\", \"r\") failed", buf); + dynstr_free(&ds_delimiter); + DBUG_VOID_RETURN; + } + + int len; + while (my_fgets(buf, sizeof(buf), res_file,&len)) + { + if (disable_result_log) + { + buf[len - 1] = 0; + DBUG_PRINT("exec_result", ("%s", buf)); + } + else + { + replace_dynstr_append_mem(&ds_res, buf, len); + } + } + error= my_pclose(res_file); + + /* Remove the temporary file, but keep it if perl failed */ + if (!error) + my_delete(temp_file_path, MYF(MY_WME)); + + /* Check for error code that indicates perl could not be started */ + int exstat= WEXITSTATUS(error); +#ifdef _WIN32 + if (exstat == 1) + /* Text must begin 'perl not found' as mtr looks for it */ + abort_not_supported_test("perl not found in path or did not start"); +#else + if (exstat == 127) + abort_not_supported_test("perl not found in path"); +#endif + else + handle_command_error(command, exstat, my_errno); + } + dynstr_free(&ds_delimiter); + DBUG_VOID_RETURN; +} + + +/* + Print the content between echo and <delimiter> to result file. + Evaluate all variables in the string before printing, allow + for variable names to be escaped using \ + + SYNOPSIS + do_echo() + command called command + + DESCRIPTION + echo text + Print the text after echo until end of command to result file + + echo $<var_name> + Print the content of the variable <var_name> to result file + + echo Some text $<var_name> + Print "Some text" plus the content of the variable <var_name> to + result file + + echo Some text \$<var_name> + Print "Some text" plus $<var_name> to result file +*/ + +int do_echo(struct st_command *command) +{ + DYNAMIC_STRING ds_echo; + DBUG_ENTER("do_echo"); + + init_dynamic_string(&ds_echo, "", command->query_len, 256); + do_eval(&ds_echo, command->first_argument, command->end, FALSE); + dynstr_append_mem(&ds_res, ds_echo.str, ds_echo.length); + dynstr_append_mem(&ds_res, "\n", 1); + dynstr_free(&ds_echo); + command->last_argument= command->end; + DBUG_RETURN(0); +} + + +void do_wait_for_slave_to_stop(struct st_command *c __attribute__((unused))) +{ + static int SLAVE_POLL_INTERVAL= 300000; + MYSQL* mysql = cur_con->mysql; + for (;;) + { + MYSQL_RES *UNINIT_VAR(res); + MYSQL_ROW row; + int done; + + if (mysql_query(mysql,"show status like 'Slave_running'") || + !(res=mysql_store_result(mysql))) + die("Query failed while probing slave for stop: %s", + mysql_error(mysql)); + if (!(row=mysql_fetch_row(res)) || !row[1]) + { + mysql_free_result(res); + die("Strange result from query while probing slave for stop"); + } + done = !strcmp(row[1],"OFF"); + mysql_free_result(res); + if (done) + break; + my_sleep(SLAVE_POLL_INTERVAL); + } + return; +} + + +void do_sync_with_master2(struct st_command *command, long offset, + const char *connection_name) +{ + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL *mysql= cur_con->mysql; + char query_buf[FN_REFLEN+128]; + int timeout= opt_wait_for_pos_timeout; + + if (!master_pos.file[0]) + die("Calling 'sync_with_master' without calling 'save_master_pos'"); + + sprintf(query_buf, "select master_pos_wait('%s', %ld, %d, '%s')", + master_pos.file, master_pos.pos + offset, timeout, + connection_name); + + if (mysql_query(mysql, query_buf)) + die("failed in '%s': %d: %s", query_buf, mysql_errno(mysql), + mysql_error(mysql)); + + if (!(res= mysql_store_result(mysql))) + die("mysql_store_result() returned NULL for '%s'", query_buf); + if (!(row= mysql_fetch_row(res))) + { + mysql_free_result(res); + die("empty result in %s", query_buf); + } + + int result= -99; + const char* result_str= row[0]; + if (result_str) + result= atoi(result_str); + + mysql_free_result(res); + + if (!result_str || result < 0) + { + /* master_pos_wait returned NULL or < 0 */ + fprintf(stderr, "analyze: sync_with_master\n"); + + if (!result_str) + { + /* + master_pos_wait returned NULL. This indicates that + slave SQL thread is not started, the slave's master + information is not initialized, the arguments are + incorrect, or an error has occurred + */ + die("%.*b failed: '%s' returned NULL " \ + "indicating slave SQL thread failure", + command->first_word_len, command->query, query_buf); + + } + + if (result == -1) + die("%.*b failed: '%s' returned -1 " \ + "indicating timeout after %d seconds", + command->first_word_len, command->query, query_buf, timeout); + else + die("%.*b failed: '%s' returned unknown result :%d", + command->first_word_len, command->query, query_buf, result); + } + + return; +} + +void do_sync_with_master(struct st_command *command) +{ + long offset= 0; + char *p= command->first_argument; + const char *offset_start= p; + char *start, *buff= 0; + start= const_cast<char*>(""); + + if (*offset_start) + { + for (; my_isdigit(charset_info, *p); p++) + offset = offset * 10 + *p - '0'; + + if (*p && !my_isspace(charset_info, *p) && *p != ',') + die("Invalid integer argument \"%s\"", offset_start); + + while (*p && my_isspace(charset_info, *p)) + p++; + if (*p == ',') + { + p++; + while (*p && my_isspace(charset_info, *p)) + p++; + start= buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, strlen(p)+1, + MYF(MY_WME|MY_FAE)); + get_string(&buff, &p, command); + } + command->last_argument= p; + } + do_sync_with_master2(command, offset, start); + if (buff) + my_free(start); + return; +} + + +int do_save_master_pos() +{ + MYSQL_RES *res; + MYSQL_ROW row; + MYSQL *mysql = cur_con->mysql; + const char *query; + DBUG_ENTER("do_save_master_pos"); + + if (mysql_query(mysql, query= "show master status")) + die("failed in 'show master status': %d %s", + mysql_errno(mysql), mysql_error(mysql)); + + if (!(res = mysql_store_result(mysql))) + die("mysql_store_result() returned NULL for '%s'", query); + if (!(row = mysql_fetch_row(res))) + die("empty result in show master status"); + strnmov(master_pos.file, row[0], sizeof(master_pos.file)-1); + master_pos.pos = strtoul(row[1], (char**) 0, 10); + mysql_free_result(res); + DBUG_RETURN(0); +} + + +/* + Assign the variable <var_name> with <var_val> + + SYNOPSIS + do_let() + query called command + + DESCRIPTION + let $<var_name>=<var_val><delimiter> + + <var_name> - is the string string found between the $ and = + <var_val> - is the content between the = and <delimiter>, it may span + multiple line and contain any characters except <delimiter> + <delimiter> - is a string containing of one or more chars, default is ; + + RETURN VALUES + Program will die if error detected +*/ + +void do_let(struct st_command *command) +{ + char *p= command->first_argument; + char *var_name, *var_name_end; + DYNAMIC_STRING let_rhs_expr; + DBUG_ENTER("do_let"); + + init_dynamic_string(&let_rhs_expr, "", 512, 2048); + + /* Find <var_name> */ + if (!*p) + die("Missing arguments to let"); + var_name= p; + while (*p && (*p != '=') && !my_isspace(charset_info,*p)) + p++; + var_name_end= p; + if (var_name == var_name_end || + (var_name+1 == var_name_end && *var_name == '$')) + die("Missing variable name in let"); + while (my_isspace(charset_info,*p)) + p++; + if (*p++ != '=') + die("Missing assignment operator in let"); + + /* Find start of <var_val> */ + while (*p && my_isspace(charset_info,*p)) + p++; + + do_eval(&let_rhs_expr, p, command->end, FALSE); + + command->last_argument= command->end; + /* Assign var_val to var_name */ + var_set(var_name, var_name_end, let_rhs_expr.str, + (let_rhs_expr.str + let_rhs_expr.length)); + dynstr_free(&let_rhs_expr); + revert_properties(); + DBUG_VOID_RETURN; +} + + +/* + Sleep the number of specified seconds + + SYNOPSIS + do_sleep() + q called command + real_sleep use the value from opt_sleep as number of seconds to sleep + if real_sleep is false + + DESCRIPTION + sleep <seconds> + real_sleep <seconds> + + The difference between the sleep and real_sleep commands is that sleep + uses the delay from the --sleep command-line option if there is one. + (If the --sleep option is not given, the sleep command uses the delay + specified by its argument.) The real_sleep command always uses the + delay specified by its argument. The logic is that sometimes delays are + cpu-dependent, and --sleep can be used to set this delay. real_sleep is + used for cpu-independent delays. +*/ + +int do_sleep(struct st_command *command, my_bool real_sleep) +{ + int error= 0; + char *sleep_start, *sleep_end; + double sleep_val; + char *p; + static DYNAMIC_STRING ds_sleep; + const struct command_arg sleep_args[] = { + { "sleep_delay", ARG_STRING, TRUE, &ds_sleep, "Number of seconds to sleep." } + }; + check_command_args(command, command->first_argument, sleep_args, + sizeof(sleep_args)/sizeof(struct command_arg), + ' '); + + p= ds_sleep.str; + sleep_end= ds_sleep.str + ds_sleep.length; + while (my_isspace(charset_info, *p)) + p++; + if (!*p) + die("Missing argument to %.*b", command->first_word_len, + command->query); + sleep_start= p; + /* Check that arg starts with a digit, not handled by my_strtod */ + if (!my_isdigit(charset_info, *sleep_start)) + die("Invalid argument to %.*b \"%s\"", command->first_word_len, + command->query, sleep_start); + sleep_val= my_strtod(sleep_start, &sleep_end, &error); + check_eol_junk_line(sleep_end); + if (error) + die("Invalid argument to %.*b \"%s\"", command->first_word_len, + command->query, command->first_argument); + dynstr_free(&ds_sleep); + + /* Fixed sleep time selected by --sleep option */ + if (opt_sleep >= 0 && !real_sleep) + sleep_val= opt_sleep; + + DBUG_PRINT("info", ("sleep_val: %f", sleep_val)); + if (sleep_val) + my_sleep((ulong) (sleep_val * 1000000L)); + return 0; +} + + +void do_get_file_name(struct st_command *command, + char* dest, uint dest_max_len) +{ + char *p= command->first_argument, *name; + if (!*p) + die("Missing file name argument"); + name= p; + while (*p && !my_isspace(charset_info,*p)) + p++; + if (*p) + *p++= 0; + command->last_argument= p; + strmake(dest, name, dest_max_len - 1); +} + + +void do_set_charset(struct st_command *command) +{ + char *charset_name= command->first_argument; + char *p; + + if (!charset_name || !*charset_name) + die("Missing charset name in 'character_set'"); + /* Remove end space */ + p= charset_name; + while (*p && !my_isspace(charset_info,*p)) + p++; + if(*p) + *p++= 0; + command->last_argument= p; + charset_info= get_charset_by_csname(charset_name,MY_CS_PRIMARY, + MYF(MY_WME | MY_UTF8_IS_UTF8MB3)); + if (!charset_info) + abort_not_supported_test("Test requires charset '%s'", charset_name); +} + + +/* + Run query and return one field in the result set from the + first row and <column> +*/ + +int query_get_string(MYSQL* mysql, const char* query, + int column, DYNAMIC_STRING* ds) +{ + MYSQL_RES *res= NULL; + MYSQL_ROW row; + + if (mysql_query(mysql, query)) + { + report_or_die("'%s' failed: %d %s", query, + mysql_errno(mysql), mysql_error(mysql)); + return 1; + } + if ((res= mysql_store_result(mysql)) == NULL) + { + report_or_die("Failed to store result: %d %s", + mysql_errno(mysql), mysql_error(mysql)); + return 1; + } + + if ((row= mysql_fetch_row(res)) == NULL) + { + mysql_free_result(res); + return 1; + } + init_dynamic_string(ds, (row[column] ? row[column] : "NULL"), ~0, 32); + mysql_free_result(res); + return 0; +} + + +#ifdef _WIN32 +#define SIGKILL 9 +#include <my_minidump.h> +static int my_kill(int pid, int sig) +{ + HANDLE proc; + if (sig == SIGABRT) + { + /* + Create a minidump. If process is being debugged, debug break + Otherwise, terminate. + */ + verbose_msg("Aborting %d",pid); + my_create_minidump(pid,TRUE); + proc= OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); + if(!proc) + return -1; + BOOL debugger_present; + if (CheckRemoteDebuggerPresent(proc,&debugger_present) && debugger_present) + { + if (DebugBreakProcess(proc)) + { + CloseHandle(proc); + return 0; + } + } + } + else if ((proc= OpenProcess(SYNCHRONIZE|PROCESS_TERMINATE, FALSE, pid)) == NULL) + return -1; + if (sig == 0) + { + DWORD wait_result= WaitForSingleObject(proc, 0); + CloseHandle(proc); + return wait_result == WAIT_OBJECT_0?-1:0; + } + (void)TerminateProcess(proc, 201); + CloseHandle(proc); + return 1; +} + + +/* Wait until process is gone, with timeout */ +static int wait_until_dead(int pid, int timeout) +{ + HANDLE proc= OpenProcess(SYNCHRONIZE, FALSE, pid); + if (!proc) + return 0; /* already dead */ + DBUG_ASSERT(timeout >= 0); + DBUG_ASSERT(timeout <= UINT_MAX/1000); + DWORD wait_result= WaitForSingleObject(proc, (DWORD)timeout*1000); + CloseHandle(proc); + return (int)wait_result; +} + +#else /* !_WIN32 */ + + +static int my_kill(int pid, int sig) +{ + DBUG_PRINT("info", ("Killing server, pid: %d", pid)); + return kill(pid, sig); +} + +/* + Shutdown the server of current connection and + make sure it goes away within <timeout> seconds + + NOTE! Currently only works with local server + + SYNOPSIS + do_shutdown_server() + command called command + + DESCRIPTION + shutdown_server [<timeout>] + +*/ + + +static int wait_until_dead(int pid, int timeout) +{ + DBUG_ENTER("wait_until_dead"); + /* Check that server dies */ + while (timeout--) + { + if (my_kill(pid, 0) < 0) + { + DBUG_PRINT("info", ("Process %d does not exist anymore", pid)); + DBUG_RETURN(0); + } + DBUG_PRINT("info", ("Sleeping, timeout: %d", timeout)); + /* Sleep one second */ + my_sleep(1000000L); + } + DBUG_RETURN(1); // Did not die +} +#endif /* _WIN32 */ + + +void do_shutdown_server(struct st_command *command) +{ + long timeout= opt_wait_for_pos_timeout ? opt_wait_for_pos_timeout / 5 : 300; + int pid; + DYNAMIC_STRING ds_pidfile_name; + MYSQL* mysql = cur_con->mysql; + static DYNAMIC_STRING ds_timeout; + const struct command_arg shutdown_args[] = { + {"timeout", ARG_STRING, FALSE, &ds_timeout, "Timeout before killing server"} + }; + DBUG_ENTER("do_shutdown_server"); + + /* the wait-for-pos' default based value of 'timeout' must fit to MDEV-23511 */ + compile_time_assert(default_wait_for_pos_timeout / 5 >= 60); + check_command_args(command, command->first_argument, shutdown_args, + sizeof(shutdown_args)/sizeof(struct command_arg), + ' '); + + if (ds_timeout.length) + { + char* endptr; + timeout= strtol(ds_timeout.str, &endptr, 10); + if (*endptr != '\0') + die("Illegal argument for timeout: '%s'", ds_timeout.str); + } + dynstr_free(&ds_timeout); + + /* Get the servers pid_file name and use it to read pid */ + if (query_get_string(mysql, "SHOW VARIABLES LIKE 'pid_file'", 1, + &ds_pidfile_name)) + die("Failed to get pid_file from server"); + + /* Read the pid from the file */ + { + int fd; + char buff[32]; + + if ((fd= my_open(ds_pidfile_name.str, O_RDONLY, MYF(0))) < 0) + die("Failed to open file '%s'", ds_pidfile_name.str); + dynstr_free(&ds_pidfile_name); + + if (my_read(fd, (uchar*)&buff, sizeof(buff), MYF(0)) <= 0){ + my_close(fd, MYF(0)); + die("pid file was empty"); + } + my_close(fd, MYF(0)); + + pid= atoi(buff); + if (pid == 0) + die("Pidfile didn't contain a valid number"); + } + DBUG_PRINT("info", ("Got pid %d", pid)); + + /* + If timeout == 0, it means we should kill the server hard, without + any shutdown or core (SIGKILL) + + If timeout is given, then we do things in the following order: + - mysql_shutdown() + - If server is not dead within timeout + - kill SIGABRT (to get a core) + - If server is not dead within new timeout + - kill SIGKILL + */ + + if (timeout && mysql_shutdown(mysql, SHUTDOWN_DEFAULT)) + die("mysql_shutdown failed"); + + if (!timeout || wait_until_dead(pid, timeout)) + { + if (timeout) + (void) my_kill(pid, SIGABRT); + /* Give server a few seconds to die in all cases */ + if (!timeout || wait_until_dead(pid, timeout < 5 ? 5 : timeout)) + { + (void) my_kill(pid, SIGKILL); + wait_until_dead(pid, 5); + } + } + DBUG_VOID_RETURN; +} + + +/* List of error names to error codes */ +typedef struct +{ + const char *name; + uint code; + const char *text; +} st_error; + +static st_error global_error_names[] = +{ + { "<No error>", ~0U, "" }, +#include <mysqld_ername.h> + { 0, 0, 0 } +}; + +#include <my_base.h> +static st_error handler_error_names[] = +{ + { "<No error>", UINT_MAX, "" }, +#include <handler_ername.h> + { 0, 0, 0 } +}; + +uint get_errcode_from_name(const char *error_name, const char *error_end, + st_error *e) +{ + DBUG_ENTER("get_errcode_from_name"); + DBUG_PRINT("enter", ("error_name: %s", error_name)); + + /* Loop through the array of known error names */ + for (; e->name; e++) + { + /* + If we get a match, we need to check the length of the name we + matched against in case it was longer than what we are checking + (as in ER_WRONG_VALUE vs. ER_WRONG_VALUE_COUNT). + */ + if (!strncmp(error_name, e->name, (int) (error_end - error_name)) && + (uint) strlen(e->name) == (uint) (error_end - error_name)) + { + DBUG_RETURN(e->code); + } + } + DBUG_RETURN(0); +} + + +uint get_errcode_from_name(const char *error_name, const char *error_end) +{ + uint tmp; + if ((tmp= get_errcode_from_name(error_name, error_end, + global_error_names))) + return tmp; + if ((tmp= get_errcode_from_name(error_name, error_end, + handler_error_names))) + return tmp; + die("Unknown SQL error name '%s'", error_name); + return 0; // Keep compiler happy +} + +const char *unknown_error= "<Unknown>"; + +const char *get_errname_from_code (uint error_code, st_error *e) +{ + DBUG_ENTER("get_errname_from_code"); + DBUG_PRINT("enter", ("error_code: %d", error_code)); + + if (! error_code) + { + DBUG_RETURN(""); + } + for (; e->name; e++) + { + if (e->code == error_code) + { + DBUG_RETURN(e->name); + } + } + /* Apparently, errors without known names may occur */ + DBUG_RETURN(unknown_error); +} + +const char *get_errname_from_code(uint error_code) +{ + const char *name; + if ((name= get_errname_from_code(error_code, global_error_names)) != + unknown_error) + return name; + return get_errname_from_code(error_code, handler_error_names); +} + +void do_get_errcodes(struct st_command *command) +{ + struct st_match_err *to= saved_expected_errors.err; + DBUG_ENTER("do_get_errcodes"); + + if (!*command->first_argument) + die("Missing argument(s) to 'error'"); + + /* TODO: Potentially, there is a possibility of variables + being expanded twice, e.g. + + let $errcodes = 1,\$a; + let $a = 1051; + error $errcodes; + DROP TABLE unknown_table; + ... + Got one of the listed errors + + But since it requires manual escaping, it does not seem + particularly dangerous or error-prone. + */ + DYNAMIC_STRING ds; + init_dynamic_string(&ds, 0, command->query_len + 64, 256); + do_eval(&ds, command->first_argument, command->end, !is_windows); + char *p= ds.str; + + uint count= 0; + char *next; + + do + { + char *end; + + /* Skip leading spaces */ + while (*p && *p == ' ') + p++; + + /* Find end */ + end= p; + while (*end && *end != ',' && *end != ' ') + end++; + + next=end; + + /* code to handle variables passed to mysqltest */ + if( *p == '$') + { + const char* fin; + VAR *var = var_get(p,&fin,0,0); + p=var->str_val; + end=p+var->str_val_len; + } + + if (*p == 'S') + { + char *to_ptr= to->code.sqlstate; + + /* + SQLSTATE string + - Must be SQLSTATE_LENGTH long + - May contain only digits[0-9] and _uppercase_ letters + */ + p++; /* Step past the S */ + if ((end - p) != SQLSTATE_LENGTH) + die("The sqlstate must be exactly %d chars long", SQLSTATE_LENGTH); + + /* Check sqlstate string validity */ + while (*p && p < end) + { + if (my_isdigit(charset_info, *p) || my_isupper(charset_info, *p)) + *to_ptr++= *p++; + else + die("The sqlstate may only consist of digits[0-9] " \ + "and _uppercase_ letters"); + } + + *to_ptr= 0; + to->type= ERR_SQLSTATE; + DBUG_PRINT("info", ("ERR_SQLSTATE: %s", to->code.sqlstate)); + } + else if (*p == 's') + { + die("The sqlstate definition must start with an uppercase S"); + } + else if (*p == 'E' || *p == 'W' || *p == 'H') + { + /* Error name string */ + + DBUG_PRINT("info", ("Error name: %s", p)); + to->code.errnum= get_errcode_from_name(p, end); + to->type= ERR_ERRNO; + DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); + } + else if (*p == 'e' || *p == 'w' || *p == 'h') + { + die("The error name definition must start with an uppercase E or W or H"); + } + else + { + long val; + char *start= p; + /* Check that the string passed to str2int only contain digits */ + while (*p && p != end) + { + if (!my_isdigit(charset_info, *p)) + die("Invalid argument to error: '%s' - " \ + "the errno may only consist of digits[0-9]", + command->first_argument); + p++; + } + + /* Convert the string to int */ + if (!str2int(start, 10, (long) INT_MIN, (long) INT_MAX, &val)) + die("Invalid argument to error: '%s'", command->first_argument); + + to->code.errnum= (uint) val; + to->type= ERR_ERRNO; + DBUG_PRINT("info", ("ERR_ERRNO: %d", to->code.errnum)); + } + to++; + count++; + + if (count >= (sizeof(saved_expected_errors.err) / + sizeof(struct st_match_err))) + die("Too many errorcodes specified"); + + /* Set pointer to the end of the last error code */ + p= next; + + /* Find next ',' */ + while (*p && *p != ',') + p++; + + if (*p) + p++; /* Step past ',' */ + + } while (*p); + + command->last_argument= command->first_argument; + while (*command->last_argument) + command->last_argument++; + + to->type= ERR_EMPTY; /* End of data */ + + DBUG_PRINT("info", ("Expected errors: %d", count)); + saved_expected_errors.count= count; + dynstr_free(&ds); + DBUG_VOID_RETURN; +} + + +/* + Get a string; Return ptr to end of string + Strings may be surrounded by " or ' + + If string is a '$variable', return the value of the variable. +*/ + +static char *get_string(char **to_ptr, char **from_ptr, + struct st_command *command) +{ + char c, sep; + char *to= *to_ptr, *from= *from_ptr, *start=to; + DBUG_ENTER("get_string"); + + /* Find separator */ + if (*from == '"' || *from == '\'') + sep= *from++; + else + sep=' '; /* Separated with space */ + + for ( ; (c=*from) ; from++) + { + if (c == '\\' && from[1]) + { /* Escaped character */ + /* We can't translate \0 -> ASCII 0 as replace can't handle ASCII 0 */ + switch (*++from) { + case 'n': + *to++= '\n'; + break; + case 't': + *to++= '\t'; + break; + case 'r': + *to++ = '\r'; + break; + case 'b': + *to++ = '\b'; + break; + case 'Z': /* ^Z must be escaped on Win32 */ + *to++='\032'; + break; + default: + *to++ = *from; + break; + } + } + else if (c == sep) + { + if (c == ' ' || c != *++from) + break; /* Found end of string */ + *to++=c; /* Copy duplicated separator */ + } + else + *to++=c; + } + if (*from != ' ' && *from) + die("Wrong string argument in %s", command->query); + + while (my_isspace(charset_info,*from)) /* Point to next string */ + from++; + + *to =0; /* End of string marker */ + *to_ptr= to+1; /* Store pointer to end */ + *from_ptr= from; + + /* Check if this was a variable */ + if (*start == '$') + { + const char *end= to; + VAR *var=var_get(start, &end, 0, 1); + if (var && to == (char*) end+1) + { + DBUG_PRINT("info",("var: '%s' -> '%s'", start, var->str_val)); + DBUG_RETURN(var->str_val); /* return found variable value */ + } + } + DBUG_RETURN(start); +} + + + + +/** + Change the current connection to the given st_connection, and update + $mysql_get_server_version and $CURRENT_CONNECTION accordingly. +*/ +void set_current_connection(struct st_connection *con) +{ + cur_con= con; + /* Update $mysql_get_server_version to that of current connection */ + var_set_int("$mysql_get_server_version", + mysql_get_server_version(con->mysql)); + /* Update $CURRENT_CONNECTION to the name of the current connection */ + var_set_string("$CURRENT_CONNECTION", con->name); +} + + +void select_connection_name(const char *name) +{ + DBUG_ENTER("select_connection_name"); + DBUG_PRINT("enter",("name: '%s'", name)); + st_connection *con= find_connection_by_name(name); + + if (!con) + die("connection '%s' not found in connection pool", name); + + set_current_connection(con); + + /* Connection logging if enabled */ + if (!disable_connect_log && !disable_query_log) + { + DYNAMIC_STRING *ds= &ds_res; + + dynstr_append_mem(ds, "connection ", 11); + replace_dynstr_append(ds, name); + dynstr_append_mem(ds, ";\n", 2); + } + + DBUG_VOID_RETURN; +} + + +void select_connection(struct st_command *command) +{ + DBUG_ENTER("select_connection"); + static DYNAMIC_STRING ds_connection; + const struct command_arg connection_args[] = { + { "connection_name", ARG_STRING, TRUE, &ds_connection, "Name of the connection that we switch to." } + }; + check_command_args(command, command->first_argument, connection_args, + sizeof(connection_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("info", ("changing connection: %s", ds_connection.str)); + select_connection_name(ds_connection.str); + dynstr_free(&ds_connection); + DBUG_VOID_RETURN; +} + + +void do_close_connection(struct st_command *command) +{ + DBUG_ENTER("do_close_connection"); + + struct st_connection *con; + static DYNAMIC_STRING ds_connection; + const struct command_arg close_connection_args[] = { + { "connection_name", ARG_STRING, TRUE, &ds_connection, + "Name of the connection to close." } + }; + check_command_args(command, command->first_argument, + close_connection_args, + sizeof(close_connection_args)/sizeof(struct command_arg), + ' '); + + DBUG_PRINT("enter",("connection name: '%s'", ds_connection.str)); + + if (!(con= find_connection_by_name(ds_connection.str))) + die("connection '%s' not found in connection pool", ds_connection.str); + + DBUG_PRINT("info", ("Closing connection %s", con->name)); +#ifndef EMBEDDED_LIBRARY + if (command->type == Q_DIRTY_CLOSE) + { + mariadb_cancel(con->mysql); + } +#endif /*!EMBEDDED_LIBRARY*/ + if (con->stmt) + do_stmt_close(con); + con->stmt= 0; +#ifdef EMBEDDED_LIBRARY + /* + As query could be still executed in a separate thread + we need to check if the query's thread was finished and probably wait + (embedded-server specific) + */ + emb_close_connection(con); +#endif /*EMBEDDED_LIBRARY*/ + + mysql_close(con->mysql); + con->mysql= 0; + + if (con->util_mysql) + mysql_close(con->util_mysql); + con->util_mysql= 0; + con->pending= FALSE; + + my_free(con->name); + + /* + When the connection is closed set name to CLOSED_CONNECTION + to make it possible to reuse the connection name. + */ + if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, CLOSED_CONNECTION, + MYF(MY_WME)))) + die("Out of memory"); + con->name_len= sizeof(CLOSED_CONNECTION)-1; + + if (con == cur_con) + { + /* Current connection was closed */ + var_set_int("$mysql_get_server_version", 0xFFFFFFFF); + var_set_string("$CURRENT_CONNECTION", con->name); + } + + /* Connection logging if enabled */ + if (!disable_connect_log && !disable_query_log) + { + DYNAMIC_STRING *ds= &ds_res; + + dynstr_append_mem(ds, "disconnect ", 11); + replace_dynstr_append(ds, ds_connection.str); + dynstr_append_mem(ds, ";\n", 2); + } + + dynstr_free(&ds_connection); + DBUG_VOID_RETURN; +} + + +/* + Connect to a server doing several retries if needed. + + SYNOPSIS + safe_connect() + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + + NOTE + + Sometimes in a test the client starts before + the server - to solve the problem, we try again + after some sleep if connection fails the first + time + + This function will try to connect to the given server + "opt_max_connect_retries" times and sleep "connection_retry_sleep" + seconds between attempts before finally giving up. + This helps in situation when the client starts + before the server (which happens sometimes). + It will only ignore connection errors during these retries. + +*/ + +void safe_connect(MYSQL* mysql, const char *name, const char *host, + const char *user, const char *pass, const char *db, + int port, const char *sock) +{ + int failed_attempts= 0; + + DBUG_ENTER("safe_connect"); + + verbose_msg("Connecting to server %s:%d (socket %s) as '%s'" + ", connection '%s', attempt %d ...", + host, port, sock, user, name, failed_attempts); + + mysql_options(mysql, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(mysql, MYSQL_OPT_CONNECT_ATTR_ADD, + "program_name", "mysqltest"); + while(!mysql_real_connect(mysql, host,user, pass, db, port, sock, + CLIENT_MULTI_STATEMENTS | CLIENT_REMEMBER_OPTIONS)) + { + /* + Connect failed + + Only allow retry if this was an error indicating the server + could not be contacted. Error code differs depending + on protocol/connection type + */ + + if ((mysql_errno(mysql) == CR_CONN_HOST_ERROR || + mysql_errno(mysql) == CR_CONNECTION_ERROR) && + failed_attempts < opt_max_connect_retries) + { + verbose_msg("Connect attempt %d/%d failed: %d: %s", failed_attempts, + opt_max_connect_retries, mysql_errno(mysql), + mysql_error(mysql)); + my_sleep(connection_retry_sleep); + } + else + { + if (failed_attempts > 0) + die("Could not open connection '%s' after %d attempts: %d %s", name, + failed_attempts, mysql_errno(mysql), mysql_error(mysql)); + else + die("Could not open connection '%s': %d %s", name, + mysql_errno(mysql), mysql_error(mysql)); + } + failed_attempts++; + } + verbose_msg("... Connected."); + DBUG_VOID_RETURN; +} + + +/* + Connect to a server and handle connection errors in case they occur. + + SYNOPSIS + connect_n_handle_errors() + q - context of connect "query" (command) + con - connection structure to be used + host, user, pass, - connection parameters + db, port, sock + default_db - 0 if db was explicitly passed + + DESCRIPTION + This function will try to establish a connection to server and handle + possible errors in the same manner as if "connect" was usual SQL-statement + (If error is expected it will ignore it once it occurs and log the + "statement" to the query log). + Unlike safe_connect() it won't do several attempts. + + RETURN VALUES + 1 - Connected + 0 - Not connected + +*/ + +int connect_n_handle_errors(struct st_command *command, + MYSQL* con, const char* host, + const char* user, const char* pass, + const char* db, int port, const char* sock, + my_bool default_db) +{ + DYNAMIC_STRING *ds; + int failed_attempts= 0; + + ds= &ds_res; + + /* Only log if an error is expected */ + if (command->expected_errors.count > 0 && + !disable_query_log) + { + /* + Log the connect to result log + */ + dynstr_append_mem(ds, "connect(", 8); + replace_dynstr_append(ds, host); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, user); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append(ds, pass); + dynstr_append_mem(ds, ",", 1); + if (db) + replace_dynstr_append(ds, db); + dynstr_append_mem(ds, ",", 1); + replace_dynstr_append_uint(ds, port); + dynstr_append_mem(ds, ",", 1); + if (sock) + replace_dynstr_append(ds, sock); + dynstr_append_mem(ds, ")", 1); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + /* Simplified logging if enabled */ + if (!disable_connect_log && !disable_query_log) + { + replace_dynstr_append(ds, command->query); + dynstr_append_mem(ds, ";\n", 2); + } + + mysql_options(con, MYSQL_OPT_CONNECT_ATTR_RESET, 0); + mysql_options4(con, MYSQL_OPT_CONNECT_ATTR_ADD, "program_name", "mysqltest"); + while (!mysql_real_connect(con, host, user, pass, + (default_db ? "" : db), + port, (sock ? sock : 0), + CLIENT_MULTI_STATEMENTS)) + { + /* + If we have used up all our connections check whether this + is expected (by --error). If so, handle the error right away. + Otherwise, give it some extra time to rule out race-conditions. + If extra-time doesn't help, we have an unexpected error and + must abort -- just proceeding to handle_error() when second + and third chances are used up will handle that for us. + + There are various user-limits of which only max_user_connections + and max_connections_per_hour apply at connect time. For the + the second to create a race in our logic, we'd need a limits + test that runs without a FLUSH for longer than an hour, so we'll + stay clear of trying to work out which exact user-limit was + exceeded. + */ + + if (((mysql_errno(con) == ER_TOO_MANY_USER_CONNECTIONS) || + (mysql_errno(con) == ER_USER_LIMIT_REACHED)) && + (failed_attempts++ < opt_max_connect_retries)) + { + int i; + + i= match_expected_error(command, mysql_errno(con), mysql_sqlstate(con)); + + if (i >= 0) + goto do_handle_error; /* expected error, handle */ + + my_sleep(connection_retry_sleep); /* unexpected error, wait */ + continue; /* and give it 1 more chance */ + } + +do_handle_error: + var_set_errno(mysql_errno(con)); + handle_error(command, mysql_errno(con), mysql_error(con), + mysql_sqlstate(con), ds); + return 0; /* Not connected */ + } + + if (default_db && db && db[0] != '\0') + { + mysql_select_db(con, db); + // Ignore errors intentionally + } + + + var_set_errno(0); + handle_no_error(command); + revert_properties(); + return 1; /* Connected */ +} + + +/* + Open a new connection to MySQL Server with the parameters + specified. Make the new connection the current connection. + + SYNOPSIS + do_connect() + q called command + + DESCRIPTION + connect(<name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]); + connect <name>,<host>,<user>,[<pass>,[<db>,[<port>,<sock>[<opts>]]]]; + + <name> - name of the new connection + <host> - hostname of server + <user> - user to connect as + <pass> - password used when connecting + <db> - initial db when connected + <port> - server port + <sock> - server socket + <opts> - options to use for the connection + * SSL - use SSL if available + * COMPRESS - use compression if available + * PIPE - use named pipe if available + +*/ + +enum use_ssl +{ + USE_SSL_FORBIDDEN = -1, + USE_SSL_IF_POSSIBLE, + USE_SSL_REQUIRED +}; + +void do_connect(struct st_command *command) +{ + uint protocol= opt_protocol; + int con_port= opt_port; + char *con_options; + char *ssl_cipher __attribute__((unused))= 0; + enum use_ssl con_ssl __attribute__((unused))= USE_SSL_IF_POSSIBLE; + my_bool con_compress= 0; + int read_timeout= 0; + int write_timeout= 0; + int connect_timeout= 0; + char *csname=0; + struct st_connection* con_slot; + my_bool default_db; + + static DYNAMIC_STRING ds_connection_name; + static DYNAMIC_STRING ds_host; + static DYNAMIC_STRING ds_user; + static DYNAMIC_STRING ds_password; + static DYNAMIC_STRING ds_database; + static DYNAMIC_STRING ds_port; + static DYNAMIC_STRING ds_sock; + static DYNAMIC_STRING ds_options; + static DYNAMIC_STRING ds_default_auth; + const struct command_arg connect_args[] = { + { "connection name", ARG_STRING, TRUE, &ds_connection_name, "Name of the connection" }, + { "host", ARG_STRING, TRUE, &ds_host, "Host to connect to" }, + { "user", ARG_STRING, FALSE, &ds_user, "User to connect as" }, + { "password", ARG_STRING, FALSE, &ds_password, "Password used when connecting" }, + { "database", ARG_STRING, FALSE, &ds_database, "Database to select after connect" }, + { "port", ARG_STRING, FALSE, &ds_port, "Port to connect to" }, + { "socket", ARG_STRING, FALSE, &ds_sock, "Socket to connect with" }, + { "options", ARG_STRING, FALSE, &ds_options, "Options to use while connecting" }, + { "default_auth", ARG_STRING, FALSE, &ds_default_auth, "Default authentication to use" } + }; + + DBUG_ENTER("do_connect"); + DBUG_PRINT("enter",("connect: %s", command->first_argument)); + + strip_parentheses(command); + check_command_args(command, command->first_argument, connect_args, + sizeof(connect_args)/sizeof(struct command_arg), + ','); + + /* Port */ + if (ds_port.length) + { + con_port= atoi(ds_port.str); + if (con_port == 0) + die("Illegal argument for port: '%s'", ds_port.str); + } + + /* Sock */ + if (ds_sock.length) + { + /* + If the socket is specified just as a name without path + or an abstract socket indicator ('@'), then + append tmpdir in front + */ + if (*ds_sock.str != FN_LIBCHAR && *ds_sock.str != '@') + { + char buff[FN_REFLEN]; + fn_format(buff, ds_sock.str, TMPDIR, "", 0); + dynstr_set(&ds_sock, buff); + } + } + else + { + /* No socket specified, use default */ + dynstr_set(&ds_sock, unix_sock); + } + DBUG_PRINT("info", ("socket: %s", ds_sock.str)); + + + /* Options */ + con_options= ds_options.str; + while (*con_options) + { + size_t length; + char *end; + /* Step past any spaces in beginning of option*/ + while (*con_options && my_isspace(charset_info, *con_options)) + con_options++; + /* Find end of this option */ + end= con_options; + while (*end && !my_isspace(charset_info, *end)) + end++; + length= (size_t) (end - con_options); + if (length == 3 && !strncmp(con_options, "SSL", 3)) + con_ssl= USE_SSL_REQUIRED; + else if (length == 5 && !strncmp(con_options, "NOSSL", 5)) + con_ssl= USE_SSL_FORBIDDEN; + else if (!strncmp(con_options, "SSL-CIPHER=", 11)) + { + con_ssl= USE_SSL_REQUIRED; + ssl_cipher=con_options + 11; + } + else if (length == 8 && !strncmp(con_options, "COMPRESS", 8)) + con_compress= 1; + else if (length == 3 && !strncmp(con_options, "TCP", 3)) + protocol= MYSQL_PROTOCOL_TCP; + else if (length == 7 && !strncmp(con_options, "DEFAULT", 7)) + protocol= MYSQL_PROTOCOL_DEFAULT; + else if (length == 4 && !strncmp(con_options, "PIPE", 4)) + { +#ifdef _WIN32 + protocol= MYSQL_PROTOCOL_PIPE; +#endif + } + else if (length == 6 && !strncmp(con_options, "SOCKET", 6)) + { +#ifndef _WIN32 + protocol= MYSQL_PROTOCOL_SOCKET; +#endif + } + else if (length == 6 && !strncmp(con_options, "MEMORY", 6)) + { +#ifdef _WIN32 + protocol= MYSQL_PROTOCOL_MEMORY; +#endif + } + else if (strncasecmp(con_options, "read_timeout=", + sizeof("read_timeout=")-1) == 0) + { + read_timeout= atoi(con_options + sizeof("read_timeout=")-1); + } + else if (strncasecmp(con_options, "write_timeout=", + sizeof("write_timeout=")-1) == 0) + { + write_timeout= atoi(con_options + sizeof("write_timeout=")-1); + } + else if (strncasecmp(con_options, "connect_timeout=", + sizeof("connect_timeout=")-1) == 0) + { + connect_timeout= atoi(con_options + sizeof("connect_timeout=")-1); + } + else if (strncasecmp(con_options, "CHARSET=", + sizeof("CHARSET=") - 1) == 0) + { + csname= strdup(con_options + sizeof("CHARSET=") - 1); + } + else + die("Illegal option to connect: %.*b", + (int) (end - con_options), con_options); + /* Process next option */ + con_options= end; + } + + if (find_connection_by_name(ds_connection_name.str)) + die("Connection %s already exists", ds_connection_name.str); + + if (next_con != connections_end) + con_slot= next_con; + else + { + if (!(con_slot= find_connection_by_name(CLOSED_CONNECTION))) + die("Connection limit exhausted, you can have max %d connections", + opt_max_connections); + my_free(con_slot->name); + con_slot->name= 0; + } + + init_connection_thd(con_slot); + + if (!(con_slot->mysql= mysql_init(0))) + die("Failed on mysql_init()"); + + if (opt_connect_timeout) + mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, + (void *) &opt_connect_timeout); + if (mysql_options(con_slot->mysql, MYSQL_OPT_NONBLOCK, 0)) + die("Failed to initialise non-blocking API"); + if (opt_compress || con_compress) + mysql_options(con_slot->mysql, MYSQL_OPT_COMPRESS, NullS); + mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_NAME, + csname ? csname : charset_info->cs_name.str); + if (opt_charsets_dir) + mysql_options(con_slot->mysql, MYSQL_SET_CHARSET_DIR, + opt_charsets_dir); + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + if (con_ssl == USE_SSL_IF_POSSIBLE && opt_use_ssl) + con_ssl= USE_SSL_REQUIRED; + + if (con_ssl == USE_SSL_REQUIRED) + { + mysql_ssl_set(con_slot->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, ssl_cipher ? ssl_cipher : opt_ssl_cipher); + mysql_options(con_slot->mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(con_slot->mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(con_slot->mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + mysql_options(con_slot->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); + } +#endif + + if (protocol) + mysql_options(con_slot->mysql, MYSQL_OPT_PROTOCOL, (char*) &protocol); + + if (read_timeout) + { + mysql_options(con_slot->mysql, MYSQL_OPT_READ_TIMEOUT, + (char*)&read_timeout); + } + + if (write_timeout) + { + mysql_options(con_slot->mysql, MYSQL_OPT_WRITE_TIMEOUT, + (char*)&write_timeout); + } + + if (connect_timeout) + { + mysql_options(con_slot->mysql, MYSQL_OPT_CONNECT_TIMEOUT, + (char*)&connect_timeout); + } + + /* Use default db name */ + if (ds_database.length == 0) + { + dynstr_set(&ds_database, opt_db); + default_db= 1; + } + else + default_db= 0; + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(con_slot->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + + if (ds_default_auth.length) + mysql_options(con_slot->mysql, MYSQL_DEFAULT_AUTH, ds_default_auth.str); + + /* Special database to allow one to connect without a database name */ + if (ds_database.length && !strcmp(ds_database.str,"*NO-ONE*")) + dynstr_set(&ds_database, ""); + + if (connect_n_handle_errors(command, con_slot->mysql, + ds_host.str,ds_user.str, + ds_password.str, ds_database.str, + con_port, ds_sock.str, default_db)) + { + DBUG_PRINT("info", ("Inserting connection %s in connection pool", + ds_connection_name.str)); + if (!(con_slot->name= my_strdup(PSI_NOT_INSTRUMENTED, ds_connection_name.str, MYF(MY_WME)))) + die("Out of memory"); + con_slot->name_len= strlen(con_slot->name); + set_current_connection(con_slot); + + if (con_slot == next_con) + next_con++; /* if we used the next_con slot, advance the pointer */ + } + else // Failed to connect. Free the memory. + { + mysql_close(con_slot->mysql); + con_slot->mysql= NULL; + } + + dynstr_free(&ds_connection_name); + dynstr_free(&ds_host); + dynstr_free(&ds_user); + dynstr_free(&ds_password); + dynstr_free(&ds_database); + dynstr_free(&ds_port); + dynstr_free(&ds_sock); + dynstr_free(&ds_options); + dynstr_free(&ds_default_auth); + free(csname); + DBUG_VOID_RETURN; +} + + +int do_done(struct st_command *command) +{ + /* Check if empty block stack */ + if (cur_block == block_stack) + { + if (*command->query != '}') + die("Stray 'end' command - end of block before beginning"); + die("Stray '}' - end of block before beginning"); + } + + /* Test if inner block has been executed */ + if (cur_block->ok && cur_block->cmd == cmd_while) + { + /* Pop block from stack, re-execute outer block */ + cur_block--; + parser.current_line = cur_block->line; + } + else + { + if (*cur_block->delim) + { + /* Restore "old" delimiter after false if block */ + if (safe_strcpy(delimiter, sizeof(delimiter), cur_block->delim)) + die("Delimiter too long, truncated"); + + delimiter_length= strlen(delimiter); + } + /* Pop block from stack, goto next line */ + cur_block--; + parser.current_line++; + } + return 0; +} + +/* Operands available in if or while conditions */ + +enum block_op { + EQ_OP, + NE_OP, + GT_OP, + GE_OP, + LT_OP, + LE_OP, + ILLEG_OP +}; + + +enum block_op find_operand(const char *start) +{ + char first= *start; + char next= *(start+1); + + if (first == '=' && next == '=') + return EQ_OP; + if (first == '!' && next == '=') + return NE_OP; + if (first == '>' && next == '=') + return GE_OP; + if (first == '>') + return GT_OP; + if (first == '<' && next == '=') + return LE_OP; + if (first == '<') + return LT_OP; + + return ILLEG_OP; +} + + +/* + Process start of a "if" or "while" statement + + SYNOPSIS + do_block() + cmd Type of block + q called command + + DESCRIPTION + if ([!]<expr>) + { + <block statements> + } + + while ([!]<expr>) + { + <block statements> + } + + Evaluates the <expr> and if it evaluates to + greater than zero executes the following code block. + A '!' can be used before the <expr> to indicate it should + be executed if it evaluates to zero. + + <expr> can also be a simple comparison condition: + + <variable> <op> <expr> + + The left hand side must be a variable, the right hand side can be a + variable, number, string or `query`. Operands are ==, !=, <, <=, >, >=. + == and != can be used for strings, all can be used for numerical values. +*/ + +void do_block(enum block_cmd cmd, struct st_command* command) +{ + char *p= command->first_argument; + const char *expr_start, *expr_end; + VAR v; + const char *cmd_name= (cmd == cmd_while ? "while" : "if"); + my_bool not_expr= FALSE; + DBUG_ENTER("do_block"); + DBUG_PRINT("enter", ("%s", cmd_name)); + + /* Check stack overflow */ + if (cur_block == block_stack_end) + die("Nesting too deeply"); + + /* Set way to find outer block again, increase line counter */ + cur_block->line= parser.current_line++; + + /* If this block is ignored */ + if (!cur_block->ok) + { + /* Inner block should be ignored too */ + cur_block++; + cur_block->cmd= cmd; + cur_block->ok= FALSE; + cur_block->delim[0]= '\0'; + DBUG_VOID_RETURN; + } + + /* Parse and evaluate test expression */ + expr_start= strchr(p, '('); + if (!expr_start++) + die("missing '(' in %s", cmd_name); + + while (my_isspace(charset_info, *expr_start)) + expr_start++; + + /* Check for !<expr> */ + if (*expr_start == '!') + { + not_expr= TRUE; + expr_start++; /* Step past the '!', then any whitespace */ + while (*expr_start && my_isspace(charset_info, *expr_start)) + expr_start++; + } + /* Find ending ')' */ + expr_end= strrchr(expr_start, ')'); + if (!expr_end) + die("missing ')' in %s", cmd_name); + p= (char*)expr_end+1; + + while (*p && my_isspace(charset_info, *p)) + p++; + if (*p && *p != '{') + die("Missing '{' after %s. Found \"%s\"", cmd_name, p); + + var_init(&v,0,0,0,0); + + /* If expression starts with a variable, it may be a compare condition */ + + if (*expr_start == '$') + { + const char *curr_ptr= expr_end; + eval_expr(&v, expr_start, &curr_ptr, true); + while (my_isspace(charset_info, *++curr_ptr)) + {} + /* If there was nothing past the variable, skip condition part */ + if (curr_ptr == expr_end) + goto NO_COMPARE; + + enum block_op operand= find_operand(curr_ptr); + if (operand == ILLEG_OP) + die("Found junk '%.*b' after $variable in condition", + (int)(expr_end - curr_ptr), curr_ptr); + + /* We could silently allow this, but may be confusing */ + if (not_expr) + die("Negation and comparison should not be combined, please rewrite"); + + /* Skip the 1 or 2 chars of the operand, then white space */ + if (operand == LT_OP || operand == GT_OP) + { + curr_ptr++; + } + else + { + curr_ptr+= 2; + } + while (my_isspace(charset_info, *curr_ptr)) + curr_ptr++; + if (curr_ptr == expr_end) + die("Missing right operand in comparison"); + + /* Strip off trailing white space */ + while (my_isspace(charset_info, expr_end[-1])) + expr_end--; + /* strip off ' or " around the string */ + if (*curr_ptr == '\'' || *curr_ptr == '"') + { + if (expr_end[-1] != *curr_ptr) + die("Unterminated string value"); + curr_ptr++; + expr_end--; + } + VAR v2; + var_init(&v2,0,0,0,0); + eval_expr(&v2, curr_ptr, &expr_end); + + if ((operand!=EQ_OP && operand!=NE_OP) && ! (v.is_int && v2.is_int)) + die("Only == and != are supported for string values"); + + /* Now we overwrite the first variable with 0 or 1 (for false or true) */ + + switch (operand) + { + case EQ_OP: + if (v.is_int) + v.int_val= (v2.is_int && v2.int_val == v.int_val); + else + v.int_val= !strcmp (v.str_val, v2.str_val); + break; + + case NE_OP: + if (v.is_int) + v.int_val= ! (v2.is_int && v2.int_val == v.int_val); + else + v.int_val= (strcmp (v.str_val, v2.str_val) != 0); + break; + + case LT_OP: + v.int_val= (v.int_val < v2.int_val); + break; + case LE_OP: + v.int_val= (v.int_val <= v2.int_val); + break; + case GT_OP: + v.int_val= (v.int_val > v2.int_val); + break; + case GE_OP: + v.int_val= (v.int_val >= v2.int_val); + break; + case ILLEG_OP: + die("Impossible operator, this cannot happen"); + } + + v.is_int= TRUE; + var_free(&v2); + } else + { + if (*expr_start != '`' && ! my_isdigit(charset_info, *expr_start)) + die("Expression in if/while must begin with $, ` or a number"); + eval_expr(&v, expr_start, &expr_end); + } + + NO_COMPARE: + /* Define inner block */ + cur_block++; + cur_block->cmd= cmd; + if (v.is_int) + { + cur_block->ok= (v.int_val != 0); + } else + /* Any non-empty string which does not begin with 0 is also TRUE */ + { + p= v.str_val; + /* First skip any leading white space or unary -+ */ + while (*p && ((my_isspace(charset_info, *p) || *p == '-' || *p == '+'))) + p++; + + cur_block->ok= (*p && *p != '0') ? TRUE : FALSE; + } + + if (not_expr) + cur_block->ok = !cur_block->ok; + + if (cur_block->ok) + { + cur_block->delim[0]= '\0'; + } + else + { + /* Remember "old" delimiter if entering a false if block */ + if (safe_strcpy(cur_block->delim, sizeof(cur_block->delim), delimiter)) + die("Delimiter too long, truncated"); + } + + DBUG_PRINT("info", ("OK: %d", cur_block->ok)); + + var_free(&v); + DBUG_VOID_RETURN; +} + + +void do_delimiter(struct st_command* command) +{ + char* p= command->first_argument; + DBUG_ENTER("do_delimiter"); + DBUG_PRINT("enter", ("first_argument: %s", command->first_argument)); + + while (*p && my_isspace(charset_info, *p)) + p++; + + if (!(*p)) + die("Can't set empty delimiter"); + + delimiter_length= (uint)(strmake_buf(delimiter, p) - delimiter); + + DBUG_PRINT("exit", ("delimiter: %s", delimiter)); + command->last_argument= p + delimiter_length; + DBUG_VOID_RETURN; +} + + +/* + do_reset_connection + + DESCRIPTION + Reset the current session. +*/ + +static void do_reset_connection() +{ +#ifndef EMBEDDED_LIBRARY + MYSQL *mysql = cur_con->mysql; + + DBUG_ENTER("do_reset_connection"); + if (mysql_reset_connection(mysql)) + die("reset connection failed: %s", mysql_error(mysql)); + if (cur_con->stmt) + { + mysql_stmt_close(cur_con->stmt); + cur_con->stmt= NULL; + } + DBUG_VOID_RETURN; +#else + die("reset connection failed: unsupported by embedded server client library"); + return; +#endif +} + + +my_bool match_delimiter(int c, const char *delim, size_t length) +{ + uint i; + char tmp[MAX_DELIMITER_LENGTH]; + + if (c != *delim) + return 0; + + for (i= 1; i < length && + (c= my_getc(cur_file->file)) == *(delim + i); + i++) + tmp[i]= c; + + if (i == length) + return 1; /* Found delimiter */ + + /* didn't find delimiter, push back things that we read */ + my_ungetc(c); + while (i > 1) + my_ungetc(tmp[--i]); + return 0; +} + + +my_bool end_of_query(int c) +{ + return match_delimiter(c, delimiter, delimiter_length); +} + + +static inline bool is_escape_char(char c, char in_string) +{ + if (c != '\\' || in_string == '`') return false; + if (!cur_con) return true; + uint server_status= cur_con->mysql->server_status; + if (server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) return false; + return !(server_status & SERVER_STATUS_ANSI_QUOTES && in_string == '"'); +} + + +/* + Read one "line" from the file + + SYNOPSIS + read_line + + DESCRIPTION + This function actually reads several lines and adds them to the + buffer buf. It continues to read until it finds what it believes + is a complete query. + + Normally that means it will read lines until it reaches the + "delimiter" that marks end of query. Default delimiter is ';' + The function should be smart enough not to detect delimiter's + found inside strings surrounded with '"' and '\'' escaped strings. + + If the first line in a query starts with '#' or '-' this line is treated + as a comment. A comment is always terminated when end of line '\n' is + reached. + +*/ + +static size_t read_command_buflen= 0; +static const size_t max_multibyte_length= 6; + +int read_line() +{ + char c, last_quote=0, last_char= 0; + char *p= read_command_buf; + char *buf_end= read_command_buf + read_command_buflen - max_multibyte_length; + int skip_char= 0; + my_bool have_slash= FALSE; + + enum {R_NORMAL, R_Q, R_SLASH_IN_Q, + R_COMMENT, R_LINE_START} state= R_LINE_START; + DBUG_ENTER("read_line"); + + *p= 0; + start_lineno= cur_file->lineno; + DBUG_PRINT("info", ("Starting to read at lineno: %d", start_lineno)); + while (1) + { + if (p >= buf_end) + { + my_ptrdiff_t off= p - read_command_buf; + read_command_buf= (char*)my_realloc(PSI_NOT_INSTRUMENTED, read_command_buf, + read_command_buflen*2, MYF(MY_FAE)); + p= read_command_buf + off; + read_command_buflen*= 2; + buf_end= read_command_buf + read_command_buflen - max_multibyte_length; + } + + skip_char= 0; + c= my_getc(cur_file->file); + if (feof(cur_file->file)) + { + found_eof: + if (cur_file->file != stdin) + { + fclose(cur_file->file); + cur_file->file= 0; + } + my_free(cur_file->file_name); + cur_file->file_name= 0; + if (cur_file == file_stack) + { + /* We're back at the first file, check if + all { have matching } + */ + if (cur_block != block_stack) + die("Missing end of block"); + + *p= 0; + DBUG_PRINT("info", ("end of file at line %d", cur_file->lineno)); + DBUG_RETURN(1); + } + cur_file--; + start_lineno= cur_file->lineno; + continue; + } + + if (c == '\n') + { + /* Line counting is independent of state */ + cur_file->lineno++; + + /* Convert cr/lf to lf */ + if (p != read_command_buf && *(p-1) == '\r') + p--; + } + + switch(state) { + case R_NORMAL: + if (end_of_query(c)) + { + *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s' at line %d", + delimiter, cur_file->lineno)); + DBUG_RETURN(0); + } + else if ((c == '{' && + (!my_strnncoll_simple(charset_info, (const uchar*) "while", 5, + (uchar*) read_command_buf, MY_MIN(5, p - read_command_buf), 0) || + !my_strnncoll_simple(charset_info, (const uchar*) "if", 2, + (uchar*) read_command_buf, MY_MIN(2, p - read_command_buf), 0)))) + { + /* Only if and while commands can be terminated by { */ + *p++= c; + *p= 0; + DBUG_PRINT("exit", ("Found '{' indicating start of block at line %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '\'' || c == '"' || c == '`') + { + if (! have_slash) + { + last_quote= c; + state= R_Q; + } + } + have_slash= is_escape_char(c, last_quote); + break; + + case R_COMMENT: + if (c == '\n') + { + /* Comments are terminated by newline */ + *p= 0; + DBUG_PRINT("exit", ("Found newline in comment at line: %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + break; + + case R_LINE_START: + if (c == '#' || c == '-') + { + /* A # or - in the first position of the line - this is a comment */ + state = R_COMMENT; + } + else if (my_isspace(charset_info, c)) + { + if (c == '\n') + { + if (last_char == '\n') + { + /* Two new lines in a row, return empty line */ + DBUG_PRINT("info", ("Found two new lines in a row")); + *p++= c; + *p= 0; + DBUG_RETURN(0); + } + + /* Query hasn't started yet */ + start_lineno= cur_file->lineno; + DBUG_PRINT("info", ("Query hasn't started yet, start_lineno: %d", + start_lineno)); + } + + /* Skip all space at beginning of line */ + skip_char= 1; + } + else if (end_of_query(c)) + { + *p= 0; + DBUG_PRINT("exit", ("Found delimiter '%s' at line: %d", + delimiter, cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '}') + { + /* A "}" need to be by itself in the beginning of a line to terminate */ + *p++= c; + *p= 0; + DBUG_PRINT("exit", ("Found '}' in beginning of a line at line: %d", + cur_file->lineno)); + DBUG_RETURN(0); + } + else if (c == '\'' || c == '"' || c == '`') + { + last_quote= c; + state= R_Q; + } + else + state= R_NORMAL; + break; + + case R_Q: + if (c == last_quote) + state= R_NORMAL; + else if (is_escape_char(c, last_quote)) + state= R_SLASH_IN_Q; + break; + + case R_SLASH_IN_Q: + state= R_Q; + break; + + } + + last_char= c; + + if (!skip_char) + { + *p++= c; + if (charset_info->use_mb()) + { + const char *mb_start= p - 1; + /* Could be a multibyte character */ + /* See a similar code in "sql_load.cc" */ + for ( ; p < buf_end; ) + { + int charlen= charset_info->charlen(mb_start, p); + if (charlen > 0) + break; /* Full character */ + if (MY_CS_IS_TOOSMALL(charlen)) + { + /* We give up if multibyte character is started but not */ + /* completed before we pass buf_end */ + c= my_getc(cur_file->file); + if (feof(cur_file->file)) + goto found_eof; + *p++ = c; + continue; + } + DBUG_ASSERT(charlen == MY_CS_ILSEQ); + /* It was not a multiline char, push back the characters */ + /* We leave first 'c', i.e. pretend it was a normal char */ + while (p - 1 > mb_start) + my_ungetc(*--p); + break; + } + } + } + } + DBUG_RETURN(0); +} + + +/* + Convert the read query to result format version 1 + + That is: After newline, all spaces need to be skipped + unless the previous char was a quote + + This is due to an old bug that has now been fixed, but the + version 1 output format is preserved by using this function + +*/ + +void convert_to_format_v1(char* query) +{ + int last_c_was_quote= 0; + char *p= query, *to= query; + char *end= strend(query); + char last_c; + + while (p <= end) + { + if (*p == '\n' && !last_c_was_quote) + { + *to++ = *p++; /* Save the newline */ + + /* Skip any spaces on next line */ + while (*p && my_isspace(charset_info, *p)) + p++; + + last_c_was_quote= 0; + } + else if (*p == '\'' || *p == '"' || *p == '`') + { + last_c= *p; + *to++ = *p++; + + /* Copy anything until the next quote of same type */ + while (*p && *p != last_c) + *to++ = *p++; + + *to++ = *p++; + + last_c_was_quote= 1; + } + else + { + *to++ = *p++; + last_c_was_quote= 0; + } + } +} + + +/* + Check for unexpected "junk" after the end of query + This is normally caused by missing delimiters or when + switching between different delimiters +*/ + +void check_eol_junk_line(const char *line) +{ + const char *p= line; + DBUG_ENTER("check_eol_junk_line"); + DBUG_PRINT("enter", ("line: %s", line)); + + /* Check for extra delimiter */ + if (*p && !strncmp(p, delimiter, delimiter_length)) + die("Extra delimiter \"%s\" found", delimiter); + + /* Allow trailing # comment */ + if (*p && *p != '#') + { + if (*p == '\n') + die("Missing delimiter"); + die("End of line junk detected: \"%s\"", p); + } + DBUG_VOID_RETURN; +} + +void check_eol_junk(const char *eol) +{ + const char *p= eol; + DBUG_ENTER("check_eol_junk"); + DBUG_PRINT("enter", ("eol: %s", eol)); + + /* Skip past all spacing chars and comments */ + while (*p && (my_isspace(charset_info, *p) || *p == '#' || *p == '\n')) + { + /* Skip past comments started with # and ended with newline */ + if (*p && *p == '#') + { + p++; + while (*p && *p != '\n') + p++; + } + + /* Check this line */ + if (*p && *p == '\n') + check_eol_junk_line(p); + + if (*p) + p++; + } + + check_eol_junk_line(p); + + DBUG_VOID_RETURN; +} + + +bool is_delimiter(const char* p) +{ + uint match= 0; + char* delim= delimiter; + while (*p && *p == *delim++) + { + match++; + p++; + } + + return (match == delimiter_length); +} + + +/* + Create a command from a set of lines + + SYNOPSIS + read_command() + command_ptr pointer where to return the new query + + DESCRIPTION + Converts lines returned by read_line into a command, this involves + parsing the first word in the read line to find the command type. + + A -- comment may contain a valid query as the first word after the + comment start. Thus it's always checked to see if that is the case. + The advantage with this approach is to be able to execute commands + terminated by new line '\n' regardless how many "delimiter" it contain. +*/ + +int read_command(struct st_command** command_ptr) +{ + struct st_command* command; + DBUG_ENTER("read_command"); + + if (parser.current_line < parser.read_lines) + { + get_dynamic(&q_lines, command_ptr, parser.current_line) ; + DBUG_PRINT("info", ("query: %s", (*command_ptr)->query)); + DBUG_RETURN(0); + } + if (!(*command_ptr= command= + (struct st_command*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*command), + MYF(MY_WME|MY_FAE|MY_ZEROFILL))) || + insert_dynamic(&q_lines, &command)) + die("Out of memory"); + command->type= Q_UNKNOWN; + + if (read_line()) + { + check_eol_junk(read_command_buf); + DBUG_RETURN(1); + } + + if (opt_result_format_version == 1) + convert_to_format_v1(read_command_buf); + + char *p= read_command_buf; + DBUG_PRINT("info", ("query: '%s'", read_command_buf)); + if (*p == '#') + { + command->type= Q_COMMENT; + } + else if (p[0] == '-' && p[1] == '-') + { + command->type= Q_COMMENT_WITH_COMMAND; + p+= 2; /* Skip past -- */ + } + else if (*p == '\n') + { + command->type= Q_EMPTY_LINE; + } + + /* Skip leading spaces */ + while (*p && my_isspace(charset_info, *p)) + p++; + + if (!(command->query_buf= command->query= my_strdup(PSI_NOT_INSTRUMENTED, p, MYF(MY_WME)))) + die("Out of memory"); + + /* + Calculate first word length(the command), terminated + by 'space' , '(' or 'delimiter' */ + p= command->query; + while (*p && !my_isspace(charset_info, *p) && *p != '(' && !is_delimiter(p)) + p++; + command->first_word_len= (uint) (p - command->query); + DBUG_PRINT("info", ("first_word: %.*s", + command->first_word_len, command->query)); + + /* Skip spaces between command and first argument */ + while (*p && my_isspace(charset_info, *p)) + p++; + command->first_argument= p; + + command->end= strend(command->query); + command->query_len= (int)(command->end - command->query); + parser.read_lines++; + DBUG_RETURN(0); +} + + +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', "Basedir for tests.", &opt_basedir, + &opt_basedir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"character-sets-dir", 0, + "Directory for character set files.", &opt_charsets_dir, + &opt_charsets_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"compress", 'C', "Use the compressed server/client protocol.", + &opt_compress, &opt_compress, 0, GET_BOOL, NO_ARG, 0, 0, 0, + 0, 0, 0}, + {"continue-on-error", 0, + "Continue test even if we got an error. " + "This is mostly useful when testing a storage engine to see what from a test file it can execute, " + "or to find all syntax errors in a newly created big test file", + &opt_continue_on_error, &opt_continue_on_error, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"cursor-protocol", 0, "Use cursors for prepared statements.", + &cursor_protocol, &cursor_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"database", 'D', "Database to use.", &opt_db, &opt_db, 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. Often this is 'd:t:o,filename'.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, +#endif + {"debug-check", 0, "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", 0, "Print some debug info at exit.", + &debug_info_flag, &debug_info_flag, + 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"host", 'h', "Connect to host.", &opt_host, &opt_host, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"prologue", 0, "Include SQL before each test case.", &opt_prologue, + &opt_prologue, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"logdir", OPT_LOG_DIR, "Directory for log files", &opt_logdir, + &opt_logdir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"mark-progress", 0, + "Write line number and elapsed time to <testname>.progress.", + &opt_mark_progress, &opt_mark_progress, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"max-connect-retries", 0, + "Maximum number of attempts to connect to server.", + &opt_max_connect_retries, &opt_max_connect_retries, 0, + GET_INT, REQUIRED_ARG, 500, 1, 10000, 0, 0, 0}, + {"max-connections", 0, + "Max number of open connections to server", + &opt_max_connections, &opt_max_connections, 0, + GET_INT, REQUIRED_ARG, DEFAULT_MAX_CONN, 8, 5120, 0, 0, 0}, + {"password", 'p', "Password to use when connecting to server.", + 0, 0, 0, GET_STR, OPT_ARG, 0, 0, 0, 0, 0, 0}, + {"protocol", OPT_MYSQL_PROTOCOL, "The protocol of connection (tcp,socket,pipe).", + 0, 0, 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_port, &opt_port, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"ps-protocol", 0, + "Use prepared-statement protocol for communication.", + &ps_protocol, &ps_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"non-blocking-api", 0, + "Use the non-blocking client API for communication.", + &non_blocking_api_enabled, &non_blocking_api_enabled, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"quiet", 's', "Suppress all normal output.", &silent, + &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"record", 'r', "Record output of test_file into result file.", + 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"result-file", 'R', "Read/store result from/in this file.", + &result_file_name, &result_file_name, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"result-format-version", OPT_RESULT_FORMAT_VERSION, + "Version of the result file format to use", + &opt_result_format_version, + &opt_result_format_version, 0, + GET_INT, REQUIRED_ARG, 1, 1, 2, 0, 0, 0}, + {"server-arg", 'A', "Send option value to embedded server as a parameter.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"server-file", 'F', "Read embedded server arguments from file.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"silent", 's', "Suppress all normal output. Synonym for --quiet.", + &silent, &silent, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"sleep", 'T', "Always sleep this many seconds on sleep commands.", + &opt_sleep, &opt_sleep, 0, GET_INT, REQUIRED_ARG, -1, -1, 0, + 0, 0, 0}, + {"socket", 'S', "The socket file to use for connection.", + &unix_sock, &unix_sock, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, + 0, 0, 0}, + {"sp-protocol", 0, "Use stored procedures for select.", + &sp_protocol, &sp_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, +#include "sslopt-longopts.h" + {"tail-lines", 0, + "Number of lines of the result to include in a failure report.", + &opt_tail_lines, &opt_tail_lines, 0, + GET_INT, REQUIRED_ARG, 0, 0, 10000, 0, 0, 0}, + {"test-file", 'x', "Read test from/in this file (default stdin).", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"timer-file", 'm', "File where the timing in microseconds is stored.", + 0, 0, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"tmpdir", 't', "Temporary directory where sockets are put.", + 0, 0, 0, GET_STR, REQUIRED_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', "Write more.", &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}, + {"view-protocol", 0, "Use views for select.", + &view_protocol, &view_protocol, 0, + GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0}, + {"connect_timeout", 0, + "Number of seconds before connection timeout.", + &opt_connect_timeout, &opt_connect_timeout, 0, GET_UINT, REQUIRED_ARG, + 120, 0, 3600 * 12, 0, 0, 0}, + {"wait_for_pos_timeout", 0, + "Number of seconds to wait for master_pos_wait", + &opt_wait_for_pos_timeout, &opt_wait_for_pos_timeout, 0, GET_UINT, + REQUIRED_ARG, default_wait_for_pos_timeout, 0, 3600 * 12, 0, 0, 0}, + {"plugin_dir", 0, "Directory for client-side plugins.", + &opt_plugin_dir, &opt_plugin_dir, 0, + GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"overlay-dir", 0, "Overlay directory.", &opt_overlay_dir, + &opt_overlay_dir, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, + {"suite-dir", 0, "Suite directory.", &opt_suite_dir, + &opt_suite_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} +}; + + +void print_version(void) +{ + printf("%s Ver %s Distrib %s, for %s (%s)\n",my_progname,MTEST_VERSION, + MYSQL_SERVER_VERSION,SYSTEM_TYPE,MACHINE_TYPE); +} + +void usage() +{ + print_version(); + puts(ORACLE_WELCOME_COPYRIGHT_NOTICE("2000")); + printf("Runs a test against the MariaDB server and compares output with a results file.\n\n"); + printf("Usage: %s [OPTIONS] [database] < test_file\n", my_progname); + print_defaults("my",load_default_groups); + puts(""); + my_print_help(my_long_options); + my_print_variables(my_long_options); +} + + +/* + Read arguments for embedded server and put them into + embedded_server_args[] +*/ + +void read_embedded_server_arguments(const char *name) +{ + char argument[1024],buff[FN_REFLEN], *str=0; + FILE *file; + + if (!test_if_hard_path(name)) + { + strxmov(buff, opt_basedir, name, NullS); + name=buff; + } + fn_format(buff, name, "", "", MY_UNPACK_FILENAME); + + if (!embedded_server_arg_count) + { + embedded_server_arg_count=1; + embedded_server_args[0]= const_cast<char*>(""); /* Progname */ + } + if (!(file=my_fopen(buff, O_RDONLY | FILE_BINARY, MYF(MY_WME)))) + die("Failed to open file '%s'", buff); + + while (embedded_server_arg_count < MAX_EMBEDDED_SERVER_ARGS && + (str=fgets(argument,sizeof(argument), file))) + { + *(strend(str)-1)=0; /* Remove end newline */ + if (!(embedded_server_args[embedded_server_arg_count]= + my_strdup(PSI_NOT_INSTRUMENTED, str, MYF(MY_WME)))) + { + my_fclose(file,MYF(0)); + die("Out of memory"); + + } + embedded_server_arg_count++; + } + my_fclose(file,MYF(0)); + if (str) + die("Too many arguments in option file: %s",name); + + return; +} + + +static my_bool +get_one_option(const struct my_option *opt, const char *argument, const char *) +{ + switch(opt->id) { + case '#': +#ifndef DBUG_OFF + DBUG_PUSH(argument ? argument : "d:t:S:i:O,/tmp/mysqltest.trace"); + debug_check_flag= 1; + debug_info_flag= 1; +#endif + break; + case 'r': + record = 1; + break; + case 'x': + { + char buff[FN_REFLEN]; + if (!test_if_hard_path(argument)) + { + strxmov(buff, opt_basedir, argument, NullS); + argument= buff; + } + fn_format(buff, argument, "", "", MY_UNPACK_FILENAME); + DBUG_ASSERT(cur_file == file_stack && cur_file->file == 0); + if (!(cur_file->file= + fopen(buff, "rb"))) + die("Could not open '%s' for reading, errno: %d", buff, errno); + cur_file->file_name= my_strdup(PSI_NOT_INSTRUMENTED, buff, MYF(MY_FAE)); + cur_file->lineno= 1; + break; + } + case 'm': + { + static char buff[FN_REFLEN]; + if (!test_if_hard_path(argument)) + { + strxmov(buff, opt_basedir, argument, NullS); + argument= buff; + } + fn_format(buff, argument, "", "", MY_UNPACK_FILENAME); + timer_file= buff; + unlink(timer_file); /* Ignore error, may not exist */ + break; + } + case 'p': + if (argument == disabled_my_option) + argument= const_cast<char*>(""); // Don't require password + if (argument) + { + /* + One should not really change the argument, but we make an + exception for passwords + */ + my_free(opt_pass); + opt_pass= my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)); + while (*argument) + *(char*)argument++= 'x'; /* Destroy argument */ + tty_password= 0; + } + else + tty_password= 1; + break; +#include <sslopt-case.h> + case 't': + strnmov(TMPDIR, argument, sizeof(TMPDIR)); + break; + case 'A': + if (!embedded_server_arg_count) + { + embedded_server_arg_count=1; + embedded_server_args[0]= const_cast<char*>(""); + } + if (embedded_server_arg_count == MAX_EMBEDDED_SERVER_ARGS-1 || + !(embedded_server_args[embedded_server_arg_count++]= + my_strdup(PSI_NOT_INSTRUMENTED, argument, MYF(MY_FAE)))) + { + die("Can't use server argument"); + } + break; + case OPT_LOG_DIR: + /* Check that the file exists */ + if (access(opt_logdir, F_OK) != 0) + die("The specified log directory does not exist: '%s'", opt_logdir); + break; + case 'F': + read_embedded_server_arguments(argument); + break; + case OPT_RESULT_FORMAT_VERSION: + set_result_format_version(opt_result_format_version); + break; + case 'V': + print_version(); + exit(0); + case OPT_MYSQL_PROTOCOL: +#ifndef EMBEDDED_LIBRARY + if ((opt_protocol= find_type_with_warning(argument, &sql_protocol_typelib, + opt->name)) <= 0) + exit(1); +#endif + break; + case '?': + usage(); + exit(0); + } + return 0; +} + + +int parse_args(int argc, char **argv) +{ + load_defaults_or_exit("my", load_default_groups, &argc, &argv); + default_argv= argv; + + if ((handle_options(&argc, &argv, my_long_options, get_one_option))) + exit(1); + + if (argc > 1) + { + usage(); + exit(1); + } + if (argc == 1) + opt_db= *argv; + if (tty_password) + opt_pass= my_get_tty_password(NullS); /* purify tested */ + if (debug_info_flag) + my_end_arg= MY_CHECK_ERROR | MY_GIVE_INFO; + if (debug_check_flag) + my_end_arg|= MY_CHECK_ERROR; + + if (global_subst != NULL) + { + char *comma= strstr(global_subst, ","); + if (comma == NULL) + die("wrong --global-subst, must be X,Y"); + memcpy(global_subst_from, global_subst, (comma-global_subst)); + global_subst_from[comma-global_subst]= 0; + memcpy(global_subst_to, comma+1, strlen(comma)); + } + + if (!opt_suite_dir) + opt_suite_dir= "./"; + suite_dir_len= strlen(opt_suite_dir); + overlay_dir_len= opt_overlay_dir ? strlen(opt_overlay_dir) : 0; + + if (!record) + { + /* Check that the result file exists */ + if (result_file_name && access(result_file_name, F_OK) != 0) + die("The specified result file '%s' does not exist", + result_file_name); + } + + return 0; +} + +/* + Write the content of str into file + + SYNOPSIS + str_to_file2 + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file + append - append to file instead of overwriting old file +*/ + +void str_to_file2(const char *fname, char *str, size_t size, my_bool append) +{ + int fd; + char buff[FN_REFLEN]; + int flags= O_WRONLY | O_CREAT; + if (!test_if_hard_path(fname)) + { + strxmov(buff, opt_basedir, fname, NullS); + fname= buff; + } + fn_format(buff, fname, "", "", MY_UNPACK_FILENAME); + + if (!append) + flags|= O_TRUNC; + if ((fd= my_open(buff, flags, + MYF(MY_WME | MY_FFNF))) < 0) + die("Could not open '%s' for writing, errno: %d", buff, errno); + if (append && my_seek(fd, 0, SEEK_END, MYF(0)) == MY_FILEPOS_ERROR) + die("Could not find end of file '%s', errno: %d", buff, errno); + if (my_write(fd, (uchar*)str, size, MYF(MY_WME|MY_FNABP))) + die("write failed, errno: %d", errno); + my_close(fd, MYF(0)); +} + +/* + Write the content of str into file + + SYNOPSIS + str_to_file + fname - name of file to truncate/create and write to + str - content to write to file + size - size of content witten to file +*/ + +void str_to_file(const char *fname, char *str, size_t size) +{ + str_to_file2(fname, str, size, FALSE); +} + + +void check_regerr(regex_t* r, int err) +{ + char err_buf[1024]; + + if (err) + { + regerror(err,r,err_buf,sizeof(err_buf)); + die("Regex error: %s\n", err_buf); + } +} + + +#ifdef _WIN32 + +DYNAMIC_ARRAY patterns; + +/* + init_win_path_patterns + + DESCRIPTION + Setup string patterns that will be used to detect filenames that + needs to be converted from Win to Unix format + +*/ + +void init_win_path_patterns() +{ + /* List of string patterns to match in order to find paths */ + const char* paths[] = { "$MYSQL_TEST_DIR", + "$MYSQL_TMP_DIR", + "$MYSQLTEST_VARDIR", + "$MASTER_MYSOCK", + "$MYSQL_SHAREDIR", + "$MYSQL_LIBDIR", + "./test/" }; + int num_paths= sizeof(paths)/sizeof(char*); + int i; + char* p; + + DBUG_ENTER("init_win_path_patterns"); + + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &patterns, sizeof(const char*), 16, 16, MYF(0)); + + /* Loop through all paths in the array */ + for (i= 0; i < num_paths; i++) + { + VAR* v; + if (*(paths[i]) == '$') + { + v= var_get(paths[i], 0, 0, 0); + p= my_strdup(PSI_NOT_INSTRUMENTED, v->str_val, MYF(MY_FAE)); + } + else + p= my_strdup(PSI_NOT_INSTRUMENTED, paths[i], MYF(MY_FAE)); + + /* Don't insert zero length strings in patterns array */ + if (strlen(p) == 0) + { + my_free(p); + continue; + } + + if (insert_dynamic(&patterns, &p)) + die("Out of memory"); + + DBUG_PRINT("info", ("p: %s", p)); + while (*p) + { + if (*p == '/') + *p='\\'; + p++; + } + } + DBUG_VOID_RETURN; +} + +void free_win_path_patterns() +{ + uint i= 0; + for (i=0 ; i < patterns.elements ; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + my_free((void *) *pattern); + } + delete_dynamic(&patterns); +} +#endif + +/* + fix_win_paths + + DESCRIPTION + Search the string 'val' for the patterns that are known to be + strings that contain filenames. Convert all \ to / in the + filenames that are found. + + Ex: + val = 'Error "c:\mysql\mysql-test\var\test\t1.frm" didn't exist' + => $MYSQL_TEST_DIR is found by strstr + => all \ from c:\mysql\m... until next space is converted into / +*/ + +void fix_win_paths(char *val, size_t len) +{ +#ifdef _WIN32 + uint i; + char *p; + + DBUG_ENTER("fix_win_paths"); + for (i= 0; i < patterns.elements; i++) + { + const char** pattern= dynamic_element(&patterns, i, const char**); + DBUG_PRINT("info", ("pattern: %s", *pattern)); + + /* Search for the path in string */ + while ((p= strstr(val, *pattern))) + { + DBUG_PRINT("info", ("Found %s in val p: %s", *pattern, p)); + + while (*p && !my_isspace(charset_info, *p)) + { + if (*p == '\\') + *p= '/'; + p++; + } + DBUG_PRINT("info", ("Converted \\ to /, p: %s", p)); + } + } + DBUG_PRINT("exit", (" val: %s, len: %zu", val, len)); + DBUG_VOID_RETURN; +#endif +} + + + +/* + Append the result for one field to the dynamic string ds +*/ + +void append_field(DYNAMIC_STRING *ds, uint col_idx, MYSQL_FIELD* field, + char* val, size_t len, my_bool is_null) +{ + char null[]= "NULL"; + + if (col_idx < max_replace_column && replace_column[col_idx]) + { + val= replace_column[col_idx]; + len= strlen(val); + } + else if (is_null) + { + val= null; + len= 4; + } +#ifdef _WIN32 + else if ((field->type == MYSQL_TYPE_DOUBLE || + field->type == MYSQL_TYPE_FLOAT ) && + field->decimals >= 31) + { + /* Convert 1.2e+018 to 1.2e+18 and 1.2e-018 to 1.2e-18 */ + char *start= strchr(val, 'e'); + if (start && strlen(start) >= 5 && + (start[1] == '-' || start[1] == '+') && start[2] == '0') + { + start+=2; /* Now points at first '0' */ + if (field->flags & ZEROFILL_FLAG) + { + /* Move all chars before the first '0' one step right */ + memmove(val + 1, val, start - val); + *val= '0'; + } + else + { + /* Move all chars after the first '0' one step left */ + memmove(start, start + 1, strlen(start)); + len--; + } + } + } +#endif + + if (!display_result_vertically) + { + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, len); + } + else + { + dynstr_append(ds, field->name); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_mem(ds, val, len); + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append all results to the dynamic string separated with '\t' + Values may be converted with 'replace_column' +*/ + +void append_result(DYNAMIC_STRING *ds, MYSQL_RES *res) +{ + MYSQL_ROW row; + uint num_fields= mysql_num_fields(res); + MYSQL_FIELD *fields= mysql_fetch_fields(res); + ulong *lengths; + + while ((row = mysql_fetch_row(res))) + { + uint i; + lengths = mysql_fetch_lengths(res); + for (i = 0; i < num_fields; i++) + append_field(ds, i, &fields[i], + row[i], lengths[i], !row[i]); + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append all results from ps execution to the dynamic string separated + with '\t'. Values may be converted with 'replace_column' +*/ + +void append_stmt_result(DYNAMIC_STRING *ds, MYSQL_STMT *stmt, + MYSQL_FIELD *fields, uint num_fields) +{ + MYSQL_BIND *my_bind; + my_bool *is_null; + ulong *length; + uint i; + int error; + + /* Allocate array with bind structs, lengths and NULL flags */ + my_bind= (MYSQL_BIND*) my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(MYSQL_BIND), + MYF(MY_WME|MY_FAE|MY_ZEROFILL)); + length= (ulong*) my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(ulong), + MYF(MY_WME|MY_FAE)); + is_null= (my_bool*) my_malloc(PSI_NOT_INSTRUMENTED, num_fields * sizeof(my_bool), + MYF(MY_WME|MY_FAE)); + + /* Allocate data for the result of each field */ + for (i= 0; i < num_fields; i++) + { + uint max_length= fields[i].max_length + 1; + my_bind[i].buffer_type= MYSQL_TYPE_STRING; + my_bind[i].buffer= my_malloc(PSI_NOT_INSTRUMENTED, max_length, + MYF(MY_WME|MY_FAE)); + my_bind[i].buffer_length= max_length; + my_bind[i].is_null= &is_null[i]; + my_bind[i].length= &length[i]; + + DBUG_PRINT("bind", ("col[%d]: buffer_type: %d, buffer_length: %lu", + i, my_bind[i].buffer_type, my_bind[i].buffer_length)); + } + + if (mysql_stmt_bind_result(stmt, my_bind)) + die("mysql_stmt_bind_result failed: %d: %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + + while ((error=mysql_stmt_fetch(stmt)) == 0) + { + for (i= 0; i < num_fields; i++) + append_field(ds, i, &fields[i], (char*)my_bind[i].buffer, + *my_bind[i].length, *my_bind[i].is_null); + if (!display_result_vertically) + dynstr_append_mem(ds, "\n", 1); + } + + if (error != MYSQL_NO_DATA) + die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: " + "error: %d", error); + if (mysql_stmt_fetch(stmt) != MYSQL_NO_DATA) + die("mysql_fetch didn't end with MYSQL_NO_DATA from statement: %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + + for (i= 0; i < num_fields; i++) + { + /* Free data for output */ + my_free(my_bind[i].buffer); + } + /* Free array with bind structs, lengths and NULL flags */ + my_free(my_bind); + my_free(length); + my_free(is_null); +} + + +/* + Append metadata for fields to output +*/ + +void append_metadata(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) +{ + MYSQL_FIELD *field_end; + dynstr_append(ds,"Catalog\tDatabase\tTable\tTable_alias\tColumn\t" + "Column_alias\tType\tLength\tMax length\tIs_null\t" + "Flags\tDecimals\tCharsetnr\n"); + + for (field_end= field+num_fields ; + field < field_end ; + field++) + { + dynstr_append_mem(ds, field->catalog, + field->catalog_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->db, field->db_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_table, + field->org_table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->table, + field->table_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->org_name, + field->org_name_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, field->name, field->name_length); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->type); + + Client_field_metadata metadata(field); + BinaryStringBuffer<128> data_type_metadata_str; + metadata.print_data_type_related_attributes(&data_type_metadata_str); + if (data_type_metadata_str.length()) + { + dynstr_append_mem(ds, " (", 2); + dynstr_append_mem(ds, data_type_metadata_str.ptr(), + data_type_metadata_str.length()); + dynstr_append_mem(ds, ")", 1); + } + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->length); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->max_length); + dynstr_append_mem(ds, "\t", 1); + dynstr_append_mem(ds, (IS_NOT_NULL(field->flags) ? "N" : "Y"), 1); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->flags); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->decimals); + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append_uint(ds, field->charsetnr); + dynstr_append_mem(ds, "\n", 1); + } +} + + +/* + Append affected row count and other info to output +*/ + +void append_info(DYNAMIC_STRING *ds, ulonglong affected_rows, + const char *info) +{ + char buf[40], buff2[21]; + sprintf(buf,"affected rows: %s\n", llstr(affected_rows, buff2)); + dynstr_append(ds, buf); + if (info) + { + dynstr_append(ds, "info: "); + dynstr_append(ds, info); + dynstr_append_mem(ds, "\n", 1); + } +} + + +#ifndef EMBEDDED_LIBRARY +static const char *trking_info_desc[SESSION_TRACK_END + 1]= +{ + "Tracker : SESSION_TRACK_SYSTEM_VARIABLES\n", + "Tracker : SESSION_TRACK_SCHEMA\n", + "Tracker : SESSION_TRACK_STATE_CHANGE\n", + "Tracker : SESSION_TRACK_GTIDS\n", + "Tracker : SESSION_TRACK_TRANSACTION_CHARACTERISTICS\n", + "Tracker : SESSION_TRACK_TRANSACTION_TYPE\n" +#ifdef USER_VAR_TRACKING + , + "Tracker : SESSION_TRACK_MYSQL_RESERVED1\n", + "Tracker : SESSION_TRACK_MYSQL_RESERVED2\n", + "Tracker : SESSION_TRACK_MYSQL_RESERVED3\n", + "Tracker : SESSION_TRACK_MYSQL_RESERVED4\n", + "Tracker : SESSION_TRACK_MYSQL_RESERVED5\n", + "Tracker : SESSION_TRACK_MYSQL_RESERVED6\n", + "Tracker : SESSION_TRACK_USER_VARIABLES\n" +#endif // USER_VAR_TRACKING +}; +#endif // EMBEDDED_LIBRARY + +/** + @brief Append state change information (received through Ok packet) to the output. + + @param [in,out] ds Dynamic string to hold the content to be printed. + @param [in] mysql Connection handle. +*/ + +static void append_session_track_info(DYNAMIC_STRING *ds, MYSQL *mysql) +{ +#ifndef EMBEDDED_LIBRARY + for (unsigned int type= SESSION_TRACK_BEGIN; type <= SESSION_TRACK_END; type++) + { + const char *data; + size_t data_length; + + if (!mysql_session_track_get_first(mysql, + (enum_session_state_type) type, + &data, &data_length)) + { + dynstr_append(ds, "-- "); + if (type <= SESSION_TRACK_END) + { + dynstr_append(ds, trking_info_desc[type]); + } + else + { + DBUG_ASSERT(0); + dynstr_append(ds, "Tracker???\n"); + } + + dynstr_append(ds, "-- "); + dynstr_append_mem(ds, data, data_length); + } + else + continue; + while (!mysql_session_track_get_next(mysql, + (enum_session_state_type) type, + &data, &data_length)) + { + dynstr_append(ds, "\n-- "); + if (data == NULL) + { + DBUG_ASSERT(data_length == 0); + dynstr_append_mem(ds, "<NULL>", sizeof("<NULL>") - 1); + } + else + dynstr_append_mem(ds, data, data_length); + } + dynstr_append(ds, "\n\n"); + } +#endif /* EMBEDDED_LIBRARY */ +} + + +/* + Display the table headings with the names tab separated +*/ + +void append_table_headings(DYNAMIC_STRING *ds, + MYSQL_FIELD *field, + uint num_fields) +{ + uint col_idx; + if (disable_column_names) + return; + for (col_idx= 0; col_idx < num_fields; col_idx++) + { + if (col_idx) + dynstr_append_mem(ds, "\t", 1); + replace_dynstr_append(ds, field[col_idx].name); + } + dynstr_append_mem(ds, "\n", 1); +} + +/* + Fetch warnings from server and append to ds + + RETURN VALUE + Number of warnings appended to ds +*/ + +int append_warnings(DYNAMIC_STRING *ds, MYSQL* mysql) +{ + uint count; + MYSQL_RES *warn_res; + DYNAMIC_STRING res; + DBUG_ENTER("append_warnings"); + + if (!(count= mysql_warning_count(mysql))) + DBUG_RETURN(0); + DBUG_PRINT("info", ("Warnings: %ud", count)); + + /* + If one day we will support execution of multi-statements + through PS API we should not issue SHOW WARNINGS until + we have not read all results... + */ + DBUG_ASSERT(!mysql_more_results(mysql)); + + if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) + die("Error running query \"SHOW WARNINGS\": %s", mysql_error(mysql)); + + if (!(warn_res= mysql_store_result(mysql))) + die("Warning count is %u but didn't get any warnings", + count); + + init_dynamic_string(&res, "", 1024, 1024); + + append_result(&res, warn_res); + mysql_free_result(warn_res); + + DBUG_PRINT("warnings", ("%s", res.str)); + + if (display_result_sorted) + dynstr_append_sorted(ds, &res, 0); + else + dynstr_append_mem(ds, res.str, res.length); + dynstr_free(&res); + DBUG_RETURN(count); +} + + +/* + Handle situation where query is sent but there is no active connection + (e.g directly after disconnect). + + We emulate MySQL-compatible behaviour of sending something on a closed + connection. +*/ +static void handle_no_active_connection(struct st_command *command, + struct st_connection *cn, DYNAMIC_STRING *ds) +{ + handle_error(command, 2006, "MariaDB server has gone away", "000000", ds); + cn->pending= FALSE; + var_set_errno(2006); +} + +/* handler functions to execute prepared statement calls in client C API */ +void run_prepare_stmt(struct st_connection *cn, struct st_command *command, const char *query, + size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings); +void run_bind_stmt(struct st_connection *cn, struct st_command *command, const char *query, + size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings); +void run_execute_stmt(struct st_connection *cn, struct st_command *command, const char *query, + size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings); +void run_close_stmt(struct st_connection *cn, struct st_command *command, const char *query, + size_t query_len, DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings); + +/* + Run query using MySQL C API + + SYNOPSIS + run_query_normal() + mysql mysql handle + command current command pointer + flags flags indicating if we should SEND and/or REAP + query query string to execute + query_len length query string to execute + ds output buffer where to store result form query +*/ + +void run_query_normal(struct st_connection *cn, struct st_command *command, + int flags, const char *query, size_t query_len, + DYNAMIC_STRING *ds, DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= 0; + MYSQL *mysql= cn->mysql; + int err= 0, counter= 0; + DBUG_ENTER("run_query_normal"); + DBUG_PRINT("enter",("flags: %d", flags)); + DBUG_PRINT("enter", ("query: '%-.60s'", query)); + + if (!mysql) + { + handle_no_active_connection(command, cn, ds); + DBUG_VOID_RETURN; + } + + /* handle prepared statement commands */ + switch (command->type) { + case Q_PS_PREPARE: + run_prepare_stmt(cn, command, query, query_len, ds, ds_warnings); + flags &= ~QUERY_SEND_FLAG; + goto end; + break; + case Q_PS_BIND: + run_bind_stmt(cn, command, query, query_len, ds, ds_warnings); + flags &= ~QUERY_SEND_FLAG; + goto end; + break; + case Q_PS_EXECUTE: + run_execute_stmt(cn, command, query, query_len, ds, ds_warnings); + flags &= ~QUERY_SEND_FLAG; + goto end; + break; + case Q_PS_CLOSE: + run_close_stmt(cn, command, query, query_len, ds, ds_warnings); + flags &= ~QUERY_SEND_FLAG; + goto end; + break; + default: /* not a prepared statement command */ + break; + } + + if (flags & QUERY_SEND_FLAG) + { + /* + Send the query + */ + if (do_send_query(cn, query, query_len)) + { + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + } + if (!(flags & QUERY_REAP_FLAG)) + { + cn->pending= TRUE; + DBUG_VOID_RETURN; + } + + do + { + /* + When on first result set, call mysql_read_query_result to retrieve + answer to the query sent earlier + */ + if ((counter==0) && do_read_query_result(cn)) + { + /* we've failed to collect the result set */ + cn->pending= TRUE; + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + + } + + /* + Store the result of the query if it will return any fields + */ + if (mysql_field_count(mysql) && ((res= mysql_store_result(mysql)) == 0)) + { + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + + if (!disable_result_log) + { + if (res) + { + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + if (display_metadata) + append_metadata(ds, fields, num_fields); + + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); + + append_result(ds, res); + } + + /* + Need to call mysql_affected_rows() before the "new" + query to find the warnings. + */ + if (!disable_info) + append_info(ds, mysql_affected_rows(mysql), mysql_info(mysql)); + + if (display_session_track_info) + append_session_track_info(ds, mysql); + + /* + Add all warnings to the result. We can't do this if we are in + the middle of processing results from multi-statement, because + this will break protocol. + */ + if (!disable_warnings && !mysql_more_results(mysql)) + { + if (append_warnings(ds_warnings, mysql) || ds_warnings->length) + { + dynstr_append_mem(ds, "Warnings:\n", 10); + dynstr_append_mem(ds, ds_warnings->str, ds_warnings->length); + } + } + } + + if (res) + { + mysql_free_result(res); + res= 0; + } + counter++; + } while (!(err= mysql_next_result(mysql))); + if (err > 0) + { + /* We got an error from mysql_next_result, maybe expected */ + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + goto end; + } + DBUG_ASSERT(err == -1); /* Successful and there are no more results */ + + /* If we come here the query is both executed and read successfully */ + handle_no_error(command); + display_optimizer_trace(cn, ds); + revert_properties(); + +end: + + cn->pending= FALSE; + /* + We save the return code (mysql_errno(mysql)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ + var_set_errno(mysql_errno(mysql)); + DBUG_VOID_RETURN; +} + + +/* + Check whether given error is in list of expected errors + + SYNOPSIS + match_expected_error() + + PARAMETERS + command the current command (and its expect-list) + err_errno error number of the error that actually occurred + err_sqlstate SQL-state that was thrown, or NULL for impossible + (file-ops, diff, etc.) + + RETURNS + -1 for not in list, index in list of expected errors otherwise + + NOTE + If caller needs to know whether the list was empty, they should + check command->expected_errors.count. +*/ + +static int match_expected_error(struct st_command *command, + unsigned int err_errno, + const char *err_sqlstate) +{ + uint i; + + for (i= 0 ; (uint) i < command->expected_errors.count ; i++) + { + if ((command->expected_errors.err[i].type == ERR_ERRNO) && + (command->expected_errors.err[i].code.errnum == err_errno)) + return i; + + if (command->expected_errors.err[i].type == ERR_SQLSTATE) + { + /* + NULL is quite likely, but not in conjunction with a SQL-state expect! + */ + if (unlikely(err_sqlstate == NULL)) + die("expecting a SQL-state (%s) from query '%s' which cannot " + "produce one...", + command->expected_errors.err[i].code.sqlstate, command->query); + + if (strncmp(command->expected_errors.err[i].code.sqlstate, + err_sqlstate, SQLSTATE_LENGTH) == 0) + return i; + } + } + return -1; +} + + +/* + Handle errors which occurred during execution + + SYNOPSIS + handle_error() + q - query context + err_errno - error number + err_error - error message + err_sqlstate - sql state + ds - dynamic string which is used for output buffer + + NOTE + If there is an unexpected error this function will abort mysqltest + immediately. +*/ + +void handle_error(struct st_command *command, + unsigned int err_errno, const char *err_error, + const char *err_sqlstate, DYNAMIC_STRING *ds) +{ + int i; + DBUG_ENTER("handle_error"); + + var_set_int("$errno", err_errno); + + command->used_replace= 1; + if (command->require_file) + { + /* + The query after a "--require" failed. This is fine as long the server + returned a valid response. Don't allow 2013 or 2006 to trigger an + abort_not_supported_test + */ + if (err_errno == CR_SERVER_LOST || + err_errno == CR_SERVER_GONE_ERROR) + die("require query '%s' failed: %s (%d): %s", command->query, + get_errname_from_code(err_errno), + err_errno, err_error); + + /* Abort the run of this test, pass the failed query as reason */ + abort_not_supported_test("Query '%s' failed, required functionality " \ + "not supported", command->query); + } + + if (command->abort_on_error) + { + report_or_die("query '%s' failed: %s (%d): %s", command->query, + get_errname_from_code(err_errno), + err_errno, + err_error); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info", ("expected_errors.count: %d", + command->expected_errors.count)); + + i= match_expected_error(command, err_errno, err_sqlstate); + + if (i >= 0) + { + if (!disable_result_log) + { + if (command->expected_errors.count == 1) + { + /* Only log error if there is one possible error */ + dynstr_append_mem(ds, "ERROR ", 6); + replace_dynstr_append(ds, err_sqlstate); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, err_error); + dynstr_append_mem(ds,"\n",1); + } + /* Don't log error if we may not get an error */ + else if (command->expected_errors.err[0].type == ERR_SQLSTATE || + (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0)) + dynstr_append(ds,"Got one of the listed errors\n"); + } + /* OK */ + revert_properties(); + DBUG_VOID_RETURN; + } + + DBUG_PRINT("info",("i: %d expected_errors: %d", i, + command->expected_errors.count)); + + if (!disable_result_log) + { + dynstr_append_mem(ds, "ERROR ",6); + replace_dynstr_append(ds, err_sqlstate); + dynstr_append_mem(ds, ": ", 2); + replace_dynstr_append(ds, err_error); + dynstr_append_mem(ds, "\n", 1); + } + + if (command->expected_errors.count > 0) + { + if (command->expected_errors.err[0].type == ERR_ERRNO) + report_or_die("query '%s' failed with wrong errno %s (%d): '%s', " + "instead of %s (%d)...", + command->query, + get_errname_from_code(err_errno), + err_errno, err_error, + get_errname_from_code(command->expected_errors.err[0].code.errnum), + command->expected_errors.err[0].code.errnum); + else + report_or_die("query '%s' failed with wrong sqlstate %s: '%s', " + "instead of %s...", + command->query, err_sqlstate, err_error, + command->expected_errors.err[0].code.sqlstate); + } + + revert_properties(); + DBUG_VOID_RETURN; +} + + +/* + Handle absence of errors after execution + + SYNOPSIS + handle_no_error() + q - context of query + + RETURN VALUE + error - function will not return +*/ + +void handle_no_error(struct st_command *command) +{ + DBUG_ENTER("handle_no_error"); + + if (command->expected_errors.err[0].type == ERR_ERRNO && + command->expected_errors.err[0].code.errnum != 0) + { + /* Error code we wanted was != 0, i.e. not an expected success */ + report_or_die("query '%s' succeeded - should have failed with error " + "%s (%d)...", + command->query, + get_errname_from_code(command->expected_errors.err[0].code.errnum), + command->expected_errors.err[0].code.errnum); + } + else if (command->expected_errors.err[0].type == ERR_SQLSTATE && + strcmp(command->expected_errors.err[0].code.sqlstate,"00000") != 0) + { + /* SQLSTATE we wanted was != "00000", i.e. not an expected success */ + report_or_die("query '%s' succeeded - should have failed with " + "sqlstate %s...", + command->query, + command->expected_errors.err[0].code.sqlstate); + } + DBUG_VOID_RETURN; +} + + +/* + Run query using prepared statement C API + + SYNOPSIS + run_query_stmt + mysql - mysql handle + command - current command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_query_stmt(struct st_connection *cn, struct st_command *command, + char *query, size_t query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings) +{ + my_bool ignore_second_execution= 0; + MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ + MYSQL *mysql= cn->mysql; + MYSQL_STMT *stmt; + DYNAMIC_STRING ds_prepare_warnings; + DYNAMIC_STRING ds_execute_warnings; + DBUG_ENTER("run_query_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + DBUG_PRINT("info", + ("disable_warnings: %d prepare_warnings_enabled: %d", + (int) disable_warnings, (int) prepare_warnings_enabled)); + + if (!mysql) + { + handle_no_active_connection(command, cn, ds); + DBUG_VOID_RETURN; + } + + /* + Init a new stmt if it's not already one created for this connection + */ + if(!(stmt= cn->stmt)) + { + if (!(stmt= mysql_stmt_init(mysql))) + die("unable to init stmt structure"); + cn->stmt= stmt; + } + + /* Init dynamic strings for warnings */ + if (!disable_warnings) + { + init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); + init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); + } + + /* + Prepare the query + */ + if (do_stmt_prepare(cn, query, query_len)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* + Get the warnings from mysql_stmt_prepare and keep them in a + separate string + */ + if (!disable_warnings && prepare_warnings_enabled) + append_warnings(&ds_prepare_warnings, mysql); + + /* + No need to call mysql_stmt_bind_param() because we have no + parameter markers. + */ + +#if MYSQL_VERSION_ID >= 50000 + if (cursor_protocol_enabled) + { + /* + Use cursor when retrieving result + */ + ulong type= CURSOR_TYPE_READ_ONLY; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) + die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } +#endif + + /* + Execute the query first time if second execution enable + */ + if(ps2_protocol_enabled && match_re(&ps2_re, query)) + { + if (do_stmt_execute(cn)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + /* + We cannot run query twice if we get prepare warnings as these will otherwise be + disabled + */ + ignore_second_execution= (prepare_warnings_enabled && + mysql_warning_count(mysql) != 0); + } + + /* + Execute the query + */ + if (!ignore_second_execution && do_stmt_execute(cn)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + int err; + do + { + /* + When running in cursor_protocol get the warnings from execute here + and keep them in a separate string for later. + */ + if (cursor_protocol_enabled && !disable_warnings) + append_warnings(&ds_execute_warnings, mysql); + + /* + We instruct that we want to update the "max_length" field in + mysql_stmt_store_result(), this is our only way to know how much + buffer to allocate for result data + */ + { + my_bool one= 1; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) + die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } + + /* + If we got here the statement succeeded and was expected to do so, + get data. Note that this can still give errors found during execution! + Store the result of the query if if will return any fields + */ + if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + if (!disable_result_log) + { + /* + Not all statements creates a result set. If there is one we can + now create another normal result set that contains the meta + data. This set can be handled almost like any other non prepared + statement result set. + */ + if ((res= mysql_stmt_result_metadata(stmt)) != NULL) + { + /* Take the column count from meta info */ + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + if (display_metadata) + append_metadata(ds, fields, num_fields); + + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); + + append_stmt_result(ds, stmt, fields, num_fields); + + mysql_free_result(res); /* Free normal result set with meta data */ + + /* + Normally, if there is a result set, we do not show warnings from the + prepare phase. This is because some warnings are generated both during + prepare and execute; this would generate different warning output + between normal and ps-protocol test runs. + + The --enable_prepare_warnings command can be used to change this so + that warnings from both the prepare and execute phase are shown. + */ + if (!disable_warnings && !prepare_warnings_enabled) + { + DBUG_PRINT("info", ("warnings disabled")); + dynstr_set(&ds_prepare_warnings, NULL); + } + } + else + { + /* + This is a query without resultset + */ + } + + /* + Fetch info before fetching warnings, since it will be reset + otherwise. + */ + if (!disable_info) + append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql)); + + if (display_session_track_info) + append_session_track_info(ds, mysql); + + + if (!disable_warnings && !mysql_more_results(stmt->mysql)) + { + /* Get the warnings from execute */ + + /* Append warnings to ds - if there are any */ + if (append_warnings(&ds_execute_warnings, mysql) || + ds_execute_warnings.length || + ds_prepare_warnings.length || + ds_warnings->length) + { + dynstr_append_mem(ds, "Warnings:\n", 10); + if (ds_warnings->length) + dynstr_append_mem(ds, ds_warnings->str, + ds_warnings->length); + if (ds_prepare_warnings.length) + dynstr_append_mem(ds, ds_prepare_warnings.str, + ds_prepare_warnings.length); + if (ds_execute_warnings.length) + dynstr_append_mem(ds, ds_execute_warnings.str, + ds_execute_warnings.length); + } + } + } + } while ( !(err= mysql_stmt_next_result(stmt))); + + if (err > 0) + /* We got an error from mysql_next_result, maybe expected */ + handle_error(command, mysql_errno(mysql), mysql_error(mysql), + mysql_sqlstate(mysql), ds); + else + handle_no_error(command); +end: + if (!disable_warnings) + { + dynstr_free(&ds_prepare_warnings); + dynstr_free(&ds_execute_warnings); + } + + /* + We save the return code (mysql_stmt_errno(stmt)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ + + var_set_errno(mysql_stmt_errno(stmt)); + + revert_properties(); + + /* Close the statement if reconnect, need new prepare */ + { +#ifndef EMBEDDED_LIBRARY + my_bool reconnect; + mysql_get_option(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (reconnect) +#else + if (mysql->reconnect) +#endif + { + mysql_stmt_close(stmt); + cn->stmt= NULL; + } + } + + + DBUG_VOID_RETURN; +} + +/* + prepare query using prepared statement C API + + SYNPOSIS + run_prepare_stmt + mysql - mysql handle + command - current command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_prepare_stmt(struct st_connection *cn, struct st_command *command, + const char *query, size_t query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings) +{ + + MYSQL *mysql= cn->mysql; + MYSQL_STMT *stmt; + DYNAMIC_STRING ds_prepare_warnings; + DBUG_ENTER("run_prepare_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + + /* + Init a new stmt if it's not already one created for this connection + */ + if(!(stmt= cn->stmt)) + { + if (!(stmt= mysql_stmt_init(mysql))) + die("unable to init stmt structure"); + cn->stmt= stmt; + } + + /* Init dynamic strings for warnings */ + if (!disable_warnings) + { + init_dynamic_string(&ds_prepare_warnings, NULL, 0, 256); + } + + /* + Prepare the query + */ + char* PS_query= command->first_argument; + size_t PS_query_len= command->end - command->first_argument; + if (do_stmt_prepare(cn, PS_query, PS_query_len)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* + Get the warnings from mysql_stmt_prepare and keep them in a + separate string + */ + if (!disable_warnings) + { + append_warnings(&ds_prepare_warnings, mysql); + dynstr_free(&ds_prepare_warnings); + } + end: + DBUG_VOID_RETURN; +} + +/* + bind parameters for a prepared statement C API + + SYNPOSIS + run_bind_stmt + mysql - mysql handle + command - current command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_bind_stmt(struct st_connection *cn, struct st_command *command, + const char *query, size_t query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings + ) +{ + MYSQL_STMT *stmt= cn->stmt; + DBUG_ENTER("run_bind_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + MYSQL_BIND *ps_params= cn->ps_params; + if (ps_params) + { + for (size_t i=0; i<stmt->param_count; i++) + { + my_free(ps_params[i].buffer); + ps_params[i].buffer= NULL; + } + my_free(ps_params); + ps_params= NULL; + } + + /* Init PS-parameters. */ + cn->ps_params= ps_params = (MYSQL_BIND*)my_malloc(PSI_NOT_INSTRUMENTED, + sizeof(MYSQL_BIND) * + stmt->param_count, + MYF(MY_WME|MY_FAE)); + bzero((char *) ps_params, sizeof(MYSQL_BIND) * stmt->param_count); + + int i=0; + char *c; + long *l; + double *d; + + char *p= strtok((char*)command->first_argument, " "); + while (p != nullptr) + { + (void)strtol(p, &c, 10); + if (!*c) + { + ps_params[i].buffer_type= MYSQL_TYPE_LONG; + l= (long*)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(long), + MYF(MY_WME|MY_FAE)); + *l= strtol(p, &c, 10); + ps_params[i].buffer= (void*)l; + ps_params[i].buffer_length= 8; + } + else + { + (void)strtod(p, &c); + if (!*c) + { + ps_params[i].buffer_type= MYSQL_TYPE_DECIMAL; + d= (double*)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(double), + MYF(MY_WME|MY_FAE)); + *d= strtod(p, &c); + ps_params[i].buffer= (void*)d; + ps_params[i].buffer_length= 8; + } + else + { + ps_params[i].buffer_type= MYSQL_TYPE_STRING; + ps_params[i].buffer= my_strdup(PSI_NOT_INSTRUMENTED, p, + MYF(MY_WME|MY_FAE)); + ps_params[i].buffer_length= (unsigned long)strlen(p); + } + } + + p= strtok(nullptr, " "); + i++; + } + + int rc= mysql_stmt_bind_param(stmt, ps_params); + if (rc) + { + die("mysql_stmt_bind_param() failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } + + DBUG_VOID_RETURN; +} + +/* + execute query using prepared statement C API + + SYNPOSIS + run_axecute_stmt + mysql - mysql handle + command - current command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_execute_stmt(struct st_connection *cn, struct st_command *command, + const char *query, size_t query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings) +{ + MYSQL_RES *res= NULL; /* Note that here 'res' is meta data result set */ + MYSQL *mysql= cn->mysql; + MYSQL_STMT *stmt= cn->stmt; + DYNAMIC_STRING ds_execute_warnings; + DBUG_ENTER("run_execute_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + + /* Init dynamic strings for warnings */ + if (!disable_warnings) + { + init_dynamic_string(&ds_execute_warnings, NULL, 0, 256); + } + +#if MYSQL_VERSION_ID >= 50000 + if (cursor_protocol_enabled) + { + /* + Use cursor when retrieving result + */ + ulong type= CURSOR_TYPE_READ_ONLY; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_CURSOR_TYPE, (void*) &type)) + die("mysql_stmt_attr_set(STMT_ATTR_CURSOR_TYPE) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } +#endif + + /* + Execute the query + */ + if (do_stmt_execute(cn)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* + When running in cursor_protocol get the warnings from execute here + and keep them in a separate string for later. + */ + if (cursor_protocol_enabled && !disable_warnings) + append_warnings(&ds_execute_warnings, mysql); + + /* + We instruct that we want to update the "max_length" field in + mysql_stmt_store_result(), this is our only way to know how much + buffer to allocate for result data + */ + { + my_bool one= 1; + if (mysql_stmt_attr_set(stmt, STMT_ATTR_UPDATE_MAX_LENGTH, (void*) &one)) + die("mysql_stmt_attr_set(STMT_ATTR_UPDATE_MAX_LENGTH) failed': %d %s", + mysql_stmt_errno(stmt), mysql_stmt_error(stmt)); + } + + /* + If we got here the statement succeeded and was expected to do so, + get data. Note that this can still give errors found during execution! + Store the result of the query if if will return any fields + */ + if (mysql_stmt_field_count(stmt) && mysql_stmt_store_result(stmt)) + { + handle_error(command, mysql_stmt_errno(stmt), + mysql_stmt_error(stmt), mysql_stmt_sqlstate(stmt), ds); + goto end; + } + + /* If we got here the statement was both executed and read successfully */ + handle_no_error(command); + if (!disable_result_log) + { + /* + Not all statements creates a result set. If there is one we can + now create another normal result set that contains the meta + data. This set can be handled almost like any other non prepared + statement result set. + */ + if ((res= mysql_stmt_result_metadata(stmt)) != NULL) + { + /* Take the column count from meta info */ + MYSQL_FIELD *fields= mysql_fetch_fields(res); + uint num_fields= mysql_num_fields(res); + + if (display_metadata) + append_metadata(ds, fields, num_fields); + + if (!display_result_vertically) + append_table_headings(ds, fields, num_fields); + + append_stmt_result(ds, stmt, fields, num_fields); + + mysql_free_result(res); /* Free normal result set with meta data */ + + /* + Normally, if there is a result set, we do not show warnings from the + prepare phase. This is because some warnings are generated both during + prepare and execute; this would generate different warning output + between normal and ps-protocol test runs. + + The --enable_prepare_warnings command can be used to change this so + that warnings from both the prepare and execute phase are shown. + */ + } + else + { + /* + This is a query without resultset + */ + } + + /* + Fetch info before fetching warnings, since it will be reset + otherwise. + */ + if (!disable_info) + append_info(ds, mysql_stmt_affected_rows(stmt), mysql_info(mysql)); + + if (display_session_track_info) + append_session_track_info(ds, mysql); + + + if (!disable_warnings) + { + /* Get the warnings from execute */ + + /* Append warnings to ds - if there are any */ + if (append_warnings(&ds_execute_warnings, mysql) || + ds_execute_warnings.length || + ds_warnings->length) + { + dynstr_append_mem(ds, "Warnings:\n", 10); + if (ds_warnings->length) + dynstr_append_mem(ds, ds_warnings->str, + ds_warnings->length); + if (ds_execute_warnings.length) + dynstr_append_mem(ds, ds_execute_warnings.str, + ds_execute_warnings.length); + } + } + } + +end: + if (!disable_warnings) + { + dynstr_free(&ds_execute_warnings); + } + + /* + We save the return code (mysql_stmt_errno(stmt)) from the last call sent + to the server into the mysqltest builtin variable $mysql_errno. This + variable then can be used from the test case itself. + */ + + var_set_errno(mysql_stmt_errno(stmt)); + + revert_properties(); + + /* Close the statement if reconnect, need new prepare */ + { +#ifndef EMBEDDED_LIBRARY + my_bool reconnect; + mysql_get_option(mysql, MYSQL_OPT_RECONNECT, &reconnect); + if (reconnect) +#else + if (mysql->reconnect) +#endif + { + if (cn->ps_params) + { + for (size_t i=0; i<stmt->param_count; i++) + { + my_free(cn->ps_params[i].buffer); + cn->ps_params[i].buffer= NULL; + } + my_free(cn->ps_params); + } + mysql_stmt_close(stmt); + cn->stmt= NULL; + cn->ps_params= NULL; + } + } + DBUG_VOID_RETURN; +} + +/* + close a prepared statement C API + + SYNPOSIS + run_close_stmt + mysql - mysql handle + command - current command pointer + query - query string to execute + query_len - length query string to execute + ds - output buffer where to store result form query + + RETURN VALUE + error - function will not return +*/ + +void run_close_stmt(struct st_connection *cn, struct st_command *command, + const char *query, size_t query_len, DYNAMIC_STRING *ds, + DYNAMIC_STRING *ds_warnings + ) +{ + MYSQL_STMT *stmt= cn->stmt; + DBUG_ENTER("run_close_stmt"); + DBUG_PRINT("query", ("'%-.60s'", query)); + + if (cn->ps_params) + { + + for (size_t i=0; i<stmt->param_count; i++) + { + my_free(cn->ps_params[i].buffer); + cn->ps_params[i].buffer= NULL; + } + my_free(cn->ps_params); + } + + /* Close the statement */ + if (stmt) + { + mysql_stmt_close(stmt); + cn->stmt= NULL; + } + cn->ps_params= NULL; + + DBUG_VOID_RETURN; +} + + + +/* + Create a util connection if one does not already exists + and use that to run the query + This is done to avoid implicit commit when creating/dropping objects such + as view, sp etc. +*/ + +int util_query(MYSQL* org_mysql, const char* query){ + + MYSQL* mysql; + DBUG_ENTER("util_query"); + + if (service_connection_enabled) + { + if(!(mysql= cur_con->util_mysql)) + { + DBUG_PRINT("info", ("Creating util_mysql")); + if (!(mysql= mysql_init(mysql))) + die("Failed in mysql_init()"); + + if (opt_connect_timeout) + mysql_options(mysql, MYSQL_OPT_CONNECT_TIMEOUT, + (void *) &opt_connect_timeout); + + /* enable local infile, in non-binary builds often disabled by default */ + mysql_options(mysql, MYSQL_OPT_LOCAL_INFILE, 0); + mysql_options(mysql, MYSQL_OPT_NONBLOCK, 0); + safe_connect(mysql, "util", org_mysql->host, org_mysql->user, + org_mysql->passwd, org_mysql->db, org_mysql->port, + org_mysql->unix_socket); + + cur_con->util_mysql= mysql; + if (mysql->charset != org_mysql->charset) + mysql_set_character_set(mysql, org_mysql->charset-> + IF_EMBEDDED(cs_name.str, csname)); + } + } + else + mysql= org_mysql; + + int ret= mysql_query(mysql, query); + DBUG_RETURN(ret); +} + + + +/* + Run query + + SYNOPSIS + run_query() + mysql mysql handle + command current command pointer + + flags control the phased/stages of query execution to be performed + if QUERY_SEND_FLAG bit is on, the query will be sent. If QUERY_REAP_FLAG + is on the result will be read - for regular query, both bits must be on +*/ + +void run_query(struct st_connection *cn, struct st_command *command, int flags) +{ + MYSQL *mysql= cn->mysql; + DYNAMIC_STRING *ds; + DYNAMIC_STRING *save_ds= NULL; + DYNAMIC_STRING ds_result; + DYNAMIC_STRING ds_sorted; + DYNAMIC_STRING ds_warnings; + char *query; + size_t query_len; + my_bool view_created= 0, sp_created= 0; + my_bool complete_query= ((flags & QUERY_SEND_FLAG) && + (flags & QUERY_REAP_FLAG)); + DBUG_ENTER("run_query"); + + if (cn->pending && (flags & QUERY_SEND_FLAG)) + die("Cannot run query on connection between send and reap"); + + if (!(flags & QUERY_SEND_FLAG) && !cn->pending) + die("Cannot reap on a connection without pending send"); + + init_dynamic_string(&ds_warnings, NULL, 0, 256); + ds_warn= &ds_warnings; + + /* + Evaluate query if this is an eval command + */ + if (command->type == Q_EVAL || command->type == Q_SEND_EVAL || + command->type == Q_EVALP) + { + if (!command->eval_query.str) + init_dynamic_string(&command->eval_query, "", command->query_len + 256, + 1024); + else + dynstr_set(&command->eval_query, 0); + do_eval(&command->eval_query, command->query, command->end, FALSE); + query= command->eval_query.str; + query_len= command->eval_query.length; + } + else + { + query = command->query; + query_len = strlen(query); + } + + /* + When command->require_file is set the output of _this_ query + should be compared with an already existing file + Create a temporary dynamic string to contain the output from + this query. + */ + if (command->require_file) + { + init_dynamic_string(&ds_result, "", 1024, 1024); + ds= &ds_result; + } + else + ds= &ds_res; + + /* + Log the query into the output buffer + */ + if (!disable_query_log && (flags & QUERY_SEND_FLAG)) + { + char *print_query= query; + size_t print_len= query_len; + if (flags & QUERY_PRINT_ORIGINAL_FLAG) + { + print_query= command->query; + print_len= (int)(command->end - command->query); + } + replace_dynstr_append_mem(ds, print_query, print_len); + dynstr_append_mem(ds, delimiter, delimiter_length); + dynstr_append_mem(ds, "\n", 1); + } + + /* We're done with this flag */ + flags &= ~QUERY_PRINT_ORIGINAL_FLAG; + + /* + Write the command to the result file before we execute the query + This is needed to be able to analyse the log if something goes + wrong + */ + log_file.write(&ds_res); + log_file.flush(); + dynstr_set(&ds_res, 0); + + if (view_protocol_enabled && mysql && + complete_query && + match_re(&view_re, query)) + { + /* + Create the query as a view. + Use replace since view can exist from a failed mysqltest run + */ + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "CREATE OR REPLACE VIEW mysqltest_tmp_v AS ", + query_len+64, 256); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) + { + /* + Failed to create the view, this is not fatal + just run the query the normal way + */ + DBUG_PRINT("view_create_error", + ("Failed to create view '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); + + /* Log error to create view */ + verbose_msg("Failed to create view '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); + } + else + { + /* + Yes, it was possible to create this query as a view + */ + view_created= 1; + query= const_cast<char*>("SELECT * FROM mysqltest_tmp_v"); + query_len = strlen(query); + + /* + Collect warnings from create of the view that should otherwise + have been produced when the SELECT was executed + */ + append_warnings(&ds_warnings, + service_connection_enabled ? + cur_con->util_mysql : + mysql); + } + + dynstr_free(&query_str); + } + + if (sp_protocol_enabled && mysql && + complete_query && + match_re(&sp_re, query)) + { + /* + Create the query as a stored procedure + Drop first since sp can exist from a failed mysqltest run + */ + DYNAMIC_STRING query_str; + init_dynamic_string(&query_str, + "DROP PROCEDURE IF EXISTS mysqltest_tmp_sp;", + query_len+64, 256); + util_query(mysql, query_str.str); + dynstr_set(&query_str, "CREATE PROCEDURE mysqltest_tmp_sp()\n"); + dynstr_append_mem(&query_str, query, query_len); + if (util_query(mysql, query_str.str)) + { + /* + Failed to create the stored procedure for this query, + this is not fatal just run the query the normal way + */ + DBUG_PRINT("sp_create_error", + ("Failed to create sp '%s': %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql))); + + /* Log error to create sp */ + verbose_msg("Failed to create sp '%s' %d: %s", query_str.str, + mysql_errno(mysql), mysql_error(mysql)); + + } + else + { + sp_created= 1; + + query= const_cast<char*>("CALL mysqltest_tmp_sp()"); + query_len = strlen(query); + } + dynstr_free(&query_str); + } + + if (display_result_sorted) + { + /* + Collect the query output in a separate string + that can be sorted before it's added to the + global result string + */ + init_dynamic_string(&ds_sorted, "", 1024, 1024); + save_ds= ds; /* Remember original ds */ + ds= &ds_sorted; + } + + /* + Find out how to run this query + + Always run with normal C API if it's not a complete + SEND + REAP + + If it is a '?' in the query it may be a SQL level prepared + statement already and we can't do it twice + */ + if (ps_protocol_enabled && + complete_query && + /* + Check that a statement is not one of PREPARE FROM, EXECUTE, + DEALLOCATE PREPARE (possibly prefixed with the 'SET STATEMENT ... FOR' + clause. These statement shouldn't be run using prepared statement C API. + All other statements can be run using prepared statement C API. + */ + !match_re(&ps_re, query)) + run_query_stmt(cn, command, query, query_len, ds, &ds_warnings); + else + run_query_normal(cn, command, flags, query, query_len, + ds, &ds_warnings); + + dynstr_free(&ds_warnings); + ds_warn= 0; + + if (display_result_sorted) + { + /* Sort the result set and append it to result */ + dynstr_append_sorted(save_ds, &ds_sorted, 1); + ds= save_ds; + dynstr_free(&ds_sorted); + } + + if (sp_created) + { + if (util_query(mysql, "DROP PROCEDURE mysqltest_tmp_sp ")) + report_or_die("Failed to drop sp: %d: %s", mysql_errno(mysql), + mysql_error(mysql)); + } + + if (view_created) + { + if (util_query(mysql, "DROP VIEW mysqltest_tmp_v ")) + report_or_die("Failed to drop view: %d: %s", + mysql_errno(mysql), mysql_error(mysql)); + } + + if (command->require_file) + { + /* A result file was specified for _this_ query + and the output should be checked against an already + existing file which has been specified using --require or --result + */ + check_require(ds, command->require_file); + } + + if (ds == &ds_result) + dynstr_free(&ds_result); + DBUG_VOID_RETURN; +} + +/****************************************************************************/ +/* + Functions to detect different SQL statements +*/ + +char *re_eprint(int err) +{ + static char epbuf[100]; + size_t len __attribute__((unused))= + regerror(err, (regex_t *)NULL, epbuf, sizeof(epbuf)); + assert(len <= sizeof(epbuf)); + return(epbuf); +} + +void init_re_comp(regex_t *re, const char* str) +{ + int err= regcomp(re, str, (REG_EXTENDED | REG_ICASE | REG_NOSUB | REG_DOTALL)); + if (err) + { + char erbuf[100]; + size_t len= regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), (int)len, (int)sizeof(erbuf), erbuf); + } +} + +void init_re(void) +{ + /* + * Prior to the task MDEV-16708 a value of the string ps_re_str contained + * a regular expression to match statements that SHOULD BE run in PS mode. + * The task MDEV-16708 modifies interpretation of this regular expression + * and now it is used for matching statements that SHOULDN'T be run in + * PS mode. These statement are PREPARE FROM, EXECUTE, DEALLOCATE PREPARE + * possibly prefixed with the clause SET STATEMENT ... FOR + */ + const char *ps_re_str = + "^(" + "[[:space:]]*PREPARE[[:space:]]|" + "[[:space:]]*EXECUTE[[:space:]]|" + "[[:space:]]*DEALLOCATE[[:space:]]+PREPARE[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+PREPARE[[:space:]]|" + "(SET[[:space:]]+STATEMENT[[:space:]]+.+[[:space:]]+FOR[[:space:]]+)?" + "EXECUTE[[:space:]]+|" + "(SET[[:space:]]+STATEMENT[[:space:]]+.+[[:space:]]+FOR[[:space:]]+)?" + "PREPARE[[:space:]]+" + ")"; + + /* + Filter for queries that can be run using the + Stored procedures + */ + const char *sp_re_str = + "^(" + "[[:space:]]*ALTER[[:space:]]+SEQUENCE[[:space:]]|" + "[[:space:]]*ALTER[[:space:]]+TABLE[[:space:]]|" + "[[:space:]]*ALTER[[:space:]]+USER[[:space:]]|" + "[[:space:]]*ANALYZE[[:space:]]|" + "[[:space:]]*ASSIGN[[:space:]]|" + //"[[:space:]]*CALL[[:space:]]|" // XXX run_query_stmt doesn't read multiple result sets + "[[:space:]]*CHANGE[[:space:]]|" + "[[:space:]]*CHECKSUM[[:space:]]|" + "[[:space:]]*COMMIT[[:space:]]|" + "[[:space:]]*COMPOUND[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+DATABASE[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+INDEX[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+ROLE[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+SEQUENCE[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+TABLE[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+USER[[:space:]]|" + "[[:space:]]*CREATE[[:space:]]+VIEW[[:space:]]|" + "[[:space:]]*DELETE[[:space:]]|" + "[[:space:]]*DO[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+DATABASE[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+INDEX[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+ROLE[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+SEQUENCE[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+TABLE[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+USER[[:space:]]|" + "[[:space:]]*DROP[[:space:]]+VIEW[[:space:]]|" + "[[:space:]]*FLUSH[[:space:]]|" + "[[:space:]]*GRANT[[:space:]]|" + "[[:space:]]*HANDLER[[:space:]]+.*[[:space:]]+READ[[:space:]]|" + "[[:space:]]*INSERT[[:space:]]|" + "[[:space:]]*INSTALL[[:space:]]+|" + "[[:space:]]*KILL[[:space:]]|" + "[[:space:]]*OPTIMIZE[[:space:]]|" + "[[:space:]]*PRELOAD[[:space:]]|" + "[[:space:]]*RENAME[[:space:]]+TABLE[[:space:]]|" + "[[:space:]]*RENAME[[:space:]]+USER[[:space:]]|" + "[[:space:]]*REPAIR[[:space:]]|" + "[[:space:]]*REPLACE[[:space:]]|" + "[[:space:]]*RESET[[:space:]]|" + "[[:space:]]*REVOKE[[:space:]]|" + "[[:space:]]*ROLLBACK[[:space:]]|" + "[[:space:]]*SELECT[[:space:]]|" + "[[:space:]]*SET[[:space:]]+OPTION[[:space:]]|" + "[[:space:]]*SHOW[[:space:]]|" + "[[:space:]]*SHUTDOWN[[:space:]]|" + "[[:space:]]*SLAVE[[:space:]]|" + "[[:space:]]*TRUNCATE[[:space:]]|" + "[[:space:]]*UNINSTALL[[:space:]]+|" + "[[:space:]]*UPDATE[[:space:]]" + ")"; + /* + Filter for queries that can be run for second + execution of prepare statement + */ + const char *ps2_re_str = + "^(" + "[[:space:]]*SELECT[[:space:]])"; + + /* + Filter for queries that can be run as views + */ + const char *view_re_str = + "^(" + "[[:space:]]*SELECT[[:space:]])"; + + init_re_comp(&ps_re, ps_re_str); + init_re_comp(&ps2_re, ps2_re_str); + init_re_comp(&sp_re, sp_re_str); + init_re_comp(&view_re, view_re_str); +} + + +int match_re(regex_t *re, char *str) +{ + while (my_isspace(charset_info, *str)) + str++; + if (str[0] == '/' && str[1] == '*') + { + char *comm_end= strstr (str, "*/"); + if (! comm_end) + die("Statement is unterminated comment"); + str= comm_end + 2; + } + + int err= regexec(re, str, (size_t)0, NULL, 0); + + if (err == 0) + return 1; + else if (err == REG_NOMATCH) + return 0; + + { + char erbuf[100]; + size_t len= regerror(err, re, erbuf, sizeof(erbuf)); + die("error %s, %d/%d `%s'\n", + re_eprint(err), (int)len, (int)sizeof(erbuf), erbuf); + } + return 0; +} + +void free_re(void) +{ + regfree(&ps_re); + regfree(&ps2_re); + regfree(&sp_re); + regfree(&view_re); +} + +/****************************************************************************/ + +void get_command_type(struct st_command* command) +{ + char save; + uint type; + DBUG_ENTER("get_command_type"); + + if (*command->query == '}') + { + command->type = Q_END_BLOCK; + DBUG_VOID_RETURN; + } + + save= command->query[command->first_word_len]; + command->query[command->first_word_len]= 0; + type= find_type(command->query, &command_typelib, FIND_TYPE_NO_PREFIX); + command->query[command->first_word_len]= save; + if (type > 0) + { + command->type=(enum enum_commands) type; /* Found command */ + + /* + Look for case where "query" was explicitly specified to + force command being sent to server + */ + if (type == Q_QUERY) + { + /* Skip the "query" part */ + command->query= command->first_argument; + } + } + else + { + /* No mysqltest command matched */ + + if (command->type != Q_COMMENT_WITH_COMMAND) + { + /* A query that will sent to mysqld */ + command->type= Q_QUERY; + } + else + { + /* -- "comment" that didn't contain a mysqltest command */ + report_or_die("Found line beginning with -- that didn't contain " \ + "a valid mysqltest command, check your syntax or " \ + "use # if you intended to write a comment"); + command->type= Q_COMMENT; + } + } + + /* Set expected error on command */ + memcpy(&command->expected_errors, &saved_expected_errors, + sizeof(saved_expected_errors)); + DBUG_PRINT("info", ("There are %d expected errors", + command->expected_errors.count)); + DBUG_VOID_RETURN; +} + + + +/* + Record how many milliseconds it took to execute the test file + up until the current line and write it to .progress file + +*/ + +void mark_progress(struct st_command* command __attribute__((unused)), + int line) +{ + static ulonglong progress_start= 0; // < Beware + DYNAMIC_STRING ds_progress; + + char buf[32], *end; + ulonglong timer= timer_now(); + if (!progress_start) + progress_start= timer; + timer-= progress_start; + + if (init_dynamic_string(&ds_progress, "", 256, 256)) + die("Out of memory"); + + /* Milliseconds since start */ + end= longlong10_to_str(timer, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + dynstr_append_mem(&ds_progress, "\t", 1); + + /* Parser line number */ + end= int10_to_str(line, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + dynstr_append_mem(&ds_progress, "\t", 1); + + /* Filename */ + dynstr_append(&ds_progress, cur_file->file_name); + dynstr_append_mem(&ds_progress, ":", 1); + + /* Line in file */ + end= int10_to_str(cur_file->lineno, buf, 10); + dynstr_append_mem(&ds_progress, buf, (int)(end-buf)); + + + dynstr_append_mem(&ds_progress, "\n", 1); + + progress_file.write(&ds_progress); + + dynstr_free(&ds_progress); + +} + +#ifdef HAVE_STACKTRACE + +static void dump_backtrace(void) +{ + struct st_connection *conn= cur_con; + + fprintf(stderr, "read_command_buf (%p): ", read_command_buf); + fprintf(stderr, "%.*s\n", (int)read_command_buflen, read_command_buf); + fputc('\n', stderr); + + if (conn) + { + fprintf(stderr, "conn->name (%p): ", conn->name); + my_safe_print_str(conn->name, conn->name_len); + fputc('\n', stderr); +#ifdef EMBEDDED_LIBRARY + fprintf(stderr, "conn->cur_query (%p): ", conn->cur_query); + my_safe_print_str(conn->cur_query, conn->cur_query_len); + fputc('\n', stderr); +#endif + } + fputs("Attempting backtrace...\n", stderr); + my_print_stacktrace(NULL, (ulong)my_thread_stack_size, 0); +} + +#else + +static void dump_backtrace(void) +{ + fputs("Backtrace not available.\n", stderr); +} + +#endif + +static sig_handler signal_handler(int sig) +{ + fprintf(stderr, "mysqltest got " SIGNAL_FMT "\n", sig); + dump_backtrace(); + + fprintf(stderr, "Writing a core file...\n"); + fflush(stderr); + my_write_core(sig); +#ifndef _WIN32 + exit(1); // Shouldn't get here but just in case +#endif +} + +#ifdef _WIN32 + +LONG WINAPI exception_filter(EXCEPTION_POINTERS *exp) +{ + __try + { + my_set_exception_pointers(exp); + signal_handler(exp->ExceptionRecord->ExceptionCode); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + fputs("Got exception in exception handler!\n", stderr); + } + + return EXCEPTION_CONTINUE_SEARCH; +} + + +static void init_signal_handling(void) +{ + UINT mode; + + /* Set output destination of messages to the standard error stream. */ + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); + + /* Do not not display the a error message box. */ + mode= SetErrorMode(0) | SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX; + SetErrorMode(mode); + + SetUnhandledExceptionFilter(exception_filter); +} + +#else /* _WIN32 */ + +static void init_signal_handling(void) +{ + struct sigaction sa; + DBUG_ENTER("init_signal_handling"); + + sa.sa_flags = SA_RESETHAND | SA_NODEFER; + sigemptyset(&sa.sa_mask); + sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); + + sa.sa_handler= signal_handler; + + sigaction(SIGSEGV, &sa, NULL); + sigaction(SIGABRT, &sa, NULL); +#ifdef SIGBUS + sigaction(SIGBUS, &sa, NULL); +#endif + sigaction(SIGILL, &sa, NULL); + sigaction(SIGFPE, &sa, NULL); + DBUG_VOID_RETURN; +} + +#endif /* !_WIN32 */ + +int main(int argc, char **argv) +{ + struct st_command *command; + my_bool q_send_flag= 0, abort_flag= 0; + uint command_executed= 0, last_command_executed= 0; + char save_file[FN_REFLEN]; + bool empty_result= FALSE; + MY_INIT(argv[0]); + DBUG_ENTER("main"); + + /* mysqltest has no way to free all its memory correctly */ + sf_leaking_memory= 1; + + save_file[0]= 0; + TMPDIR[0]= 0; + + init_signal_handling(); + + /* Init expected errors */ + memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); + +#ifdef EMBEDDED_LIBRARY + /* set appropriate stack for the 'query' threads */ + (void) pthread_attr_init(&cn_thd_attrib); + pthread_attr_setstacksize(&cn_thd_attrib, DEFAULT_THREAD_STACK); +#endif /*EMBEDDED_LIBRARY*/ + + /* Init file stack */ + memset(file_stack, 0, sizeof(file_stack)); + file_stack_end= + file_stack + (sizeof(file_stack)/sizeof(struct st_test_file)) - 1; + cur_file= file_stack; + + /* Init block stack */ + memset(block_stack, 0, sizeof(block_stack)); + block_stack_end= + block_stack + (sizeof(block_stack)/sizeof(struct st_block)) - 1; + cur_block= block_stack; + cur_block->ok= TRUE; /* Outer block should always be executed */ + cur_block->cmd= cmd_none; + + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &q_lines, sizeof(struct st_command*), 1024, 1024, MYF(0)); + + if (my_hash_init2(PSI_NOT_INSTRUMENTED, &var_hash, 64, charset_info, 128, 0, + 0, get_var_key, 0, var_free, MYF(0))) + die("Variable hash initialization failed"); + + { + char path_separator[]= { FN_LIBCHAR, 0 }; + var_set_string("SYSTEM_PATH_SEPARATOR", path_separator); + } + var_set_string("MYSQL_SERVER_VERSION", MYSQL_SERVER_VERSION); + var_set_string("MYSQL_SYSTEM_TYPE", SYSTEM_TYPE); + var_set_string("MYSQL_MACHINE_TYPE", MACHINE_TYPE); + if (sizeof(void *) == 8) { + var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "64"); + } else { + var_set_string("MYSQL_SYSTEM_ARCHITECTURE", "32"); + } + + memset(&master_pos, 0, sizeof(master_pos)); + + parser.current_line= parser.read_lines= 0; + memset(&var_reg, 0, sizeof(var_reg)); + + init_builtin_echo(); +#ifdef _WIN32 + is_windows= 1; + init_win_path_patterns(); +#endif + + read_command_buf= (char*)my_malloc(PSI_NOT_INSTRUMENTED, read_command_buflen= 65536, MYF(MY_FAE)); + + init_dynamic_string(&ds_res, "", 2048, 2048); + init_alloc_root(PSI_NOT_INSTRUMENTED, &require_file_root, 1024, 1024, MYF(0)); + + parse_args(argc, argv); + + log_file.open(opt_logdir, result_file_name, ".log"); + verbose_msg("Logging to '%s'.", log_file.file_name()); + if (opt_mark_progress) + { + progress_file.open(opt_logdir, result_file_name, ".progress"); + verbose_msg("Tracing progress in '%s'.", progress_file.file_name()); + } + + /* Init connections, allocate 1 extra as buffer + 1 for default */ + connections= (struct st_connection*) + my_malloc(PSI_NOT_INSTRUMENTED, (opt_max_connections+2) * sizeof(struct st_connection), + MYF(MY_WME|MY_FAE|MY_ZEROFILL)); + connections_end= connections + opt_max_connections +1; + next_con= connections + 1; + + var_set_int("$PS_PROTOCOL", ps_protocol); + var_set_int("$NON_BLOCKING_API", non_blocking_api_enabled); + var_set_int("$SP_PROTOCOL", sp_protocol); + var_set_int("$VIEW_PROTOCOL", view_protocol); + var_set_int("$CURSOR_PROTOCOL", cursor_protocol); + + var_set_int("$ENABLED_QUERY_LOG", 1); + var_set_int("$ENABLED_ABORT_ON_ERROR", 1); + var_set_int("$ENABLED_RESULT_LOG", 1); + var_set_int("$ENABLED_CONNECT_LOG", 0); + var_set_int("$ENABLED_WARNINGS", 1); + var_set_int("$ENABLED_INFO", 0); + var_set_int("$ENABLED_METADATA", 0); + + DBUG_PRINT("info",("result_file: '%s'", + result_file_name ? result_file_name : "")); + verbose_msg("Results saved in '%s'.", + result_file_name ? result_file_name : ""); + if (mysql_server_init(embedded_server_arg_count, + embedded_server_args, + (char**) embedded_server_groups)) + die("Can't initialize MariaDB server"); + server_initialized= 1; + if (cur_file == file_stack && cur_file->file == 0) + { + cur_file->file= stdin; + cur_file->file_name= my_strdup(PSI_NOT_INSTRUMENTED, "<stdin>", MYF(MY_WME)); + cur_file->lineno= 1; + } + var_set_string("MYSQLTEST_FILE", cur_file->file_name); + init_re(); + + /* Cursor protocol implies ps protocol */ + if (cursor_protocol) + ps_protocol= 1; + + /* Enable second execution of SELECT for ps-protocol + if ps-protocol is used */ + ps2_protocol_enabled= ps_protocol; + ps_protocol_enabled= ps_protocol; + sp_protocol_enabled= sp_protocol; + view_protocol_enabled= view_protocol; + cursor_protocol_enabled= cursor_protocol; + + st_connection *con= connections; + init_connection_thd(con); + if (! (con->mysql= mysql_init(0))) + die("Failed in mysql_init()"); + if (opt_connect_timeout) + mysql_options(con->mysql, MYSQL_OPT_CONNECT_TIMEOUT, + (void *) &opt_connect_timeout); + if (opt_compress) + mysql_options(con->mysql,MYSQL_OPT_COMPRESS,NullS); + mysql_options(con->mysql, MYSQL_SET_CHARSET_NAME, + charset_info->cs_name.str); + if (opt_charsets_dir) + mysql_options(con->mysql, MYSQL_SET_CHARSET_DIR, + opt_charsets_dir); + + if (opt_protocol) + mysql_options(con->mysql,MYSQL_OPT_PROTOCOL,(char*)&opt_protocol); + + if (opt_plugin_dir && *opt_plugin_dir) + mysql_options(con->mysql, MYSQL_PLUGIN_DIR, opt_plugin_dir); + +#if defined(HAVE_OPENSSL) && !defined(EMBEDDED_LIBRARY) + + if (opt_use_ssl) + { + mysql_ssl_set(con->mysql, opt_ssl_key, opt_ssl_cert, opt_ssl_ca, + opt_ssl_capath, opt_ssl_cipher); + mysql_options(con->mysql, MYSQL_OPT_SSL_CRL, opt_ssl_crl); + mysql_options(con->mysql, MYSQL_OPT_SSL_CRLPATH, opt_ssl_crlpath); + mysql_options(con->mysql, MARIADB_OPT_TLS_VERSION, opt_tls_version); + mysql_options(con->mysql, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + &opt_ssl_verify_server_cert); + } +#endif + + if (!(con->name = my_strdup(PSI_NOT_INSTRUMENTED, "default", MYF(MY_WME)))) + die("Out of memory"); + mysql_options(con->mysql, MYSQL_OPT_NONBLOCK, 0); + + safe_connect(con->mysql, con->name, opt_host, opt_user, opt_pass, + opt_db, opt_port, unix_sock); + + /* Use all time until exit if no explicit 'start_timer' */ + timer_start= timer_now(); + + /* + Initialize $mysql_errno with -1, so we can + - distinguish it from valid values ( >= 0 ) and + - detect if there was never a command sent to the server + */ + var_set_errno(-1); + + set_current_connection(con); + + if (opt_prologue) + { + open_file(opt_prologue); + } + + verbose_msg("Start processing test commands from '%s' ...", cur_file->file_name); + while (!abort_flag && !read_command(&command)) + { + my_bool ok_to_do; + int current_line_inc = 1, processed = 0; + if (command->type == Q_UNKNOWN || command->type == Q_COMMENT_WITH_COMMAND) + get_command_type(command); + + if (parsing_disabled && + command->type != Q_ENABLE_PARSING && + command->type != Q_DISABLE_PARSING) + { + /* Parsing is disabled, silently convert this line to a comment */ + command->type= Q_COMMENT; + } + + /* (Re-)set abort_on_error for this command */ + command->abort_on_error= (command->expected_errors.count == 0 && + abort_on_error); + + /* + some commands need to be executed or at least parsed unconditionally, + because they change the grammar. + */ + ok_to_do= cur_block->ok || command->type == Q_DELIMITER + || command->type == Q_PERL; + /* + Some commands need to be "done" the first time if they may get + re-iterated over in a true context. This can only happen if there's + a while loop at some level above the current block. + */ + if (!ok_to_do) + { + if (command->type == Q_SOURCE || + command->type == Q_ERROR || + command->type == Q_WRITE_FILE || + command->type == Q_APPEND_FILE) + { + for (struct st_block *stb= cur_block-1; stb >= block_stack; stb--) + { + if (stb->cmd == cmd_while) + { + ok_to_do= 1; + break; + } + } + } + } + + if (ok_to_do) + { + command->last_argument= command->first_argument; + processed = 1; + /* Need to remember this for handle_error() */ + curr_command= command; + switch (command->type) { + case Q_CONNECT: + do_connect(command); + break; + case Q_CONNECTION: select_connection(command); break; + case Q_DISCONNECT: + case Q_DIRTY_CLOSE: + do_close_connection(command); break; + case Q_ENABLE_PREPARE_WARNINGS: prepare_warnings_enabled=1; break; + case Q_DISABLE_PREPARE_WARNINGS: prepare_warnings_enabled=0; break; + case Q_ENABLE_QUERY_LOG: + set_property(command, P_QUERY, 0); + break; + case Q_DISABLE_QUERY_LOG: + set_property(command, P_QUERY, 1); + break; + case Q_ENABLE_ABORT_ON_ERROR: + set_property(command, P_ABORT, 1); + break; + case Q_DISABLE_ABORT_ON_ERROR: + set_property(command, P_ABORT, 0); + break; + case Q_ENABLE_RESULT_LOG: + set_property(command, P_RESULT, 0); + break; + case Q_DISABLE_RESULT_LOG: + set_property(command, P_RESULT, 1); + break; + case Q_ENABLE_CONNECT_LOG: + set_property(command, P_CONNECT, 0); + break; + case Q_DISABLE_CONNECT_LOG: + set_property(command, P_CONNECT, 1); + break; + case Q_ENABLE_WARNINGS: + set_property(command, P_WARN, 0); + break; + case Q_DISABLE_WARNINGS: + set_property(command, P_WARN, 1); + break; + case Q_ENABLE_INFO: + set_property(command, P_INFO, 0); + break; + case Q_DISABLE_INFO: + set_property(command, P_INFO, 1); + break; + case Q_ENABLE_SESSION_TRACK_INFO: + set_property(command, P_SESSION_TRACK, 1); + break; + case Q_DISABLE_SESSION_TRACK_INFO: + set_property(command, P_SESSION_TRACK, 0); + break; + case Q_ENABLE_METADATA: + set_property(command, P_META, 1); + break; + case Q_DISABLE_METADATA: + set_property(command, P_META, 0); + break; + case Q_ENABLE_COLUMN_NAMES: + disable_column_names= 0; + var_set_int("$ENABLED_COLUMN_NAMES", 0); + break; + case Q_DISABLE_COLUMN_NAMES: + disable_column_names= 1; + var_set_int("$ENABLED_COLUMN_NAMES", 1); + break; + case Q_SOURCE: do_source(command); break; + case Q_SLEEP: do_sleep(command, 0); break; + case Q_REAL_SLEEP: do_sleep(command, 1); break; + case Q_WAIT_FOR_SLAVE_TO_STOP: do_wait_for_slave_to_stop(command); break; + case Q_INC: do_modify_var(command, DO_INC); break; + case Q_DEC: do_modify_var(command, DO_DEC); break; + case Q_ECHO: do_echo(command); command_executed++; break; + case Q_SYSTEM: do_system(command); break; + case Q_REMOVE_FILE: do_remove_file(command); break; + case Q_REMOVE_FILES_WILDCARD: do_remove_files_wildcard(command); break; + case Q_MKDIR: do_mkdir(command); break; + case Q_RMDIR: do_rmdir(command); break; + case Q_LIST_FILES: do_list_files(command); break; + case Q_LIST_FILES_WRITE_FILE: + do_list_files_write_file_command(command, FALSE); + break; + case Q_LIST_FILES_APPEND_FILE: + do_list_files_write_file_command(command, TRUE); + break; + case Q_FILE_EXIST: do_file_exist(command); break; + case Q_WRITE_FILE: do_write_file(command); break; + case Q_APPEND_FILE: do_append_file(command); break; + case Q_DIFF_FILES: do_diff_files(command); break; + case Q_SEND_QUIT: do_send_quit(command); break; + case Q_CHANGE_USER: do_change_user(command); break; + case Q_CAT_FILE: do_cat_file(command); break; + case Q_COPY_FILE: do_copy_file(command); break; + case Q_MOVE_FILE: do_move_file(command); break; + case Q_CHMOD_FILE: do_chmod_file(command); break; + case Q_PERL: do_perl(command); break; + case Q_RESULT_FORMAT_VERSION: do_result_format_version(command); break; + case Q_DELIMITER: + do_delimiter(command); + break; + case Q_DISPLAY_VERTICAL_RESULTS: + display_result_vertically= TRUE; + break; + case Q_DISPLAY_HORIZONTAL_RESULTS: + display_result_vertically= FALSE; + break; + case Q_SORTED_RESULT: + /* + Turn on sorting of result set, will be reset after next + command + */ + display_result_sorted= TRUE; + break; + case Q_LOWERCASE: + /* + Turn on lowercasing of result, will be reset after next + command + */ + display_result_lower= TRUE; + break; + case Q_LET: do_let(command); break; + case Q_EVAL_RESULT: + die("'eval_result' command is deprecated"); + break; // never called but keep compiler calm + case Q_EVAL: + case Q_EVALP: + case Q_QUERY_VERTICAL: + case Q_QUERY_HORIZONTAL: + if (command->query == command->query_buf) + { + /* Skip the first part of command, i.e query_xxx */ + command->query= command->first_argument; + command->first_word_len= 0; + } + /* fall through */ + case Q_QUERY: + case Q_REAP: + case Q_PS_PREPARE: + case Q_PS_BIND: + case Q_PS_EXECUTE: + case Q_PS_CLOSE: + { + my_bool old_display_result_vertically= display_result_vertically; + /* Default is full query, both reap and send */ + int flags= QUERY_REAP_FLAG | QUERY_SEND_FLAG; + + if (q_send_flag) + { + /* Last command was an empty 'send' */ + flags= QUERY_SEND_FLAG; + q_send_flag= 0; + } + else if (command->type == Q_REAP) + { + flags= QUERY_REAP_FLAG; + } + + if (command->type == Q_EVALP) + flags |= QUERY_PRINT_ORIGINAL_FLAG; + + /* Check for special property for this query */ + display_result_vertically|= (command->type == Q_QUERY_VERTICAL); + + if (save_file[0]) + { + if (!(command->require_file= strdup_root(&require_file_root, + save_file))) + die("out of memory for require_file"); + save_file[0]= 0; + } + run_query(cur_con, command, flags); + command_executed++; + command->last_argument= command->end; + + /* Restore settings */ + display_result_vertically= old_display_result_vertically; + + break; + } + case Q_SEND: + case Q_SEND_EVAL: + if (!*command->first_argument) + { + /* + This is a send without arguments, it indicates that _next_ query + should be send only + */ + q_send_flag= 1; + break; + } + + /* Remove "send" if this is first iteration */ + if (command->query == command->query_buf) + command->query= command->first_argument; + + /* + run_query() can execute a query partially, depending on the flags. + QUERY_SEND_FLAG flag without QUERY_REAP_FLAG tells it to just send + the query and read the result some time later when reap instruction + is given on this connection. + */ + run_query(cur_con, command, QUERY_SEND_FLAG); + command_executed++; + command->last_argument= command->end; + break; + case Q_REQUIRE: + do_get_file_name(command, save_file, sizeof(save_file)); + break; + case Q_ERROR: + do_get_errcodes(command); + break; + case Q_REPLACE: + do_get_replace(command); + break; + case Q_REPLACE_REGEX: + do_get_replace_regex(command); + break; + case Q_REPLACE_COLUMN: + do_get_replace_column(command); + break; + case Q_SAVE_MASTER_POS: do_save_master_pos(); break; + case Q_SYNC_WITH_MASTER: do_sync_with_master(command); break; + case Q_SYNC_SLAVE_WITH_MASTER: + { + do_save_master_pos(); + if (*command->first_argument) + select_connection(command); + else + select_connection_name("slave"); + do_sync_with_master2(command, 0, ""); + break; + } + case Q_COMMENT: + { + command->last_argument= command->end; + + /* Don't output comments in v1 */ + if (opt_result_format_version == 1) + break; + + /* Don't output comments if query logging is off */ + if (disable_query_log) + break; + + /* Write comment's with two starting #'s to result file */ + const char* p= command->query; + if (p && *p == '#' && *(p+1) == '#') + { + dynstr_append_mem(&ds_res, command->query, command->query_len); + dynstr_append(&ds_res, "\n"); + } + break; + } + case Q_EMPTY_LINE: + /* Don't output newline in v1 */ + if (opt_result_format_version == 1) + break; + + /* Don't output newline if query logging is off */ + if (disable_query_log) + break; + + dynstr_append(&ds_res, "\n"); + break; + case Q_PING: + handle_command_error(command, mysql_ping(cur_con->mysql), -1); + break; + case Q_RESET_CONNECTION: + do_reset_connection(); + break; + case Q_OPTIMIZER_TRACE: + enable_optimizer_trace(cur_con); + break; + case Q_SEND_SHUTDOWN: + handle_command_error(command, + mysql_shutdown(cur_con->mysql, + SHUTDOWN_DEFAULT), -1); + break; + case Q_SHUTDOWN_SERVER: + do_shutdown_server(command); + break; + case Q_EXEC: + do_exec(command); + command_executed++; + break; + case Q_START_TIMER: + /* Overwrite possible earlier start of timer */ + timer_start= timer_now(); + break; + case Q_END_TIMER: + /* End timer before ending mysqltest */ + timer_output(); + break; + case Q_CHARACTER_SET: + do_set_charset(command); + break; + case Q_DISABLE_PS_PROTOCOL: + set_property(command, P_PS, 0); + /* Close any open statements */ + close_statements(); + break; + case Q_ENABLE_PS_PROTOCOL: + set_property(command, P_PS, ps_protocol); + break; + case Q_DISABLE_PS2_PROTOCOL: + set_property(command, P_PS2, 0); + break; + case Q_ENABLE_PS2_PROTOCOL: + set_property(command, P_PS2, ps_protocol); + break; + case Q_DISABLE_VIEW_PROTOCOL: + set_property(command, P_VIEW, 0); + /* Close only util connections */ + close_util_connections(); + break; + case Q_ENABLE_VIEW_PROTOCOL: + set_property(command, P_VIEW, view_protocol); + break; + case Q_DISABLE_SERVICE_CONNECTION: + set_property(command, P_CONN, 0); + /* Close only util connections */ + close_util_connections(); + break; + case Q_ENABLE_SERVICE_CONNECTION: + set_property(command, P_CONN, view_protocol); + break; + case Q_DISABLE_NON_BLOCKING_API: + non_blocking_api_enabled= 0; + break; + case Q_ENABLE_NON_BLOCKING_API: + non_blocking_api_enabled= 1; + break; + case Q_DISABLE_RECONNECT: + mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_false); + break; + case Q_ENABLE_RECONNECT: + mysql_options(cur_con->mysql, MYSQL_OPT_RECONNECT, &my_true); + /* Close any open statements - no reconnect, need new prepare */ + close_statements(); + break; + case Q_DISABLE_PARSING: + if (parsing_disabled == 0) + parsing_disabled= 1; + else + report_or_die("Parsing is already disabled"); + break; + case Q_ENABLE_PARSING: + /* + Ensure we don't get parsing_disabled < 0 as this would accidentally + disable code we don't want to have disabled + */ + if (parsing_disabled == 1) + parsing_disabled= 0; + else + report_or_die("Parsing is already enabled"); + break; + case Q_DIE: + { + char message[160]; + const char *msg; + DYNAMIC_STRING ds_echo; + + if (command->first_argument[0]) + { + /* Evaluate variables in the message */ + init_dynamic_string(&ds_echo, "", command->query_len, 256); + do_eval(&ds_echo, command->first_argument, command->end, FALSE); + strmake(message, ds_echo.str, MY_MIN(sizeof(message)-1, + ds_echo.length)); + dynstr_free(&ds_echo); + msg= message; + } + else + msg= "Explicit --die command executed"; + + /* Abort test with error code and error message */ + die("%s", msg); + break; + } + case Q_EXIT: + /* Stop processing any more commands */ + abort_flag= 1; + break; + case Q_SKIP: + /* Eval the query, thus replacing all environment variables */ + dynstr_set(&ds_res, 0); + do_eval(&ds_res, command->first_argument, command->end, FALSE); + abort_not_supported_test("%s",ds_res.str); + break; + case Q_RESULT: + die("result, deprecated command"); + break; + default: + processed= 0; + break; + } + } + + if (!processed) + { + current_line_inc= 0; + switch (command->type) { + case Q_WHILE: do_block(cmd_while, command); break; + case Q_IF: do_block(cmd_if, command); break; + case Q_END_BLOCK: do_done(command); break; + default: current_line_inc = 1; break; + } + } + else + check_eol_junk(command->last_argument); + + if (command->type != Q_ERROR && + command->type != Q_COMMENT) + { + /* + As soon as any non "error" command or comment has been executed, + the array with expected errors should be cleared + */ + memset(&saved_expected_errors, 0, sizeof(saved_expected_errors)); + } + + if (command_executed != last_command_executed || command->used_replace) + { + /* + As soon as any command has been executed, + the replace structures should be cleared + */ + free_all_replace(); + + /* Also reset "sorted_result" and "lowercase"*/ + display_result_sorted= FALSE; + display_result_lower= FALSE; + } + last_command_executed= command_executed; + + parser.current_line += current_line_inc; + if ( opt_mark_progress ) + mark_progress(command, parser.current_line); + + /* Write result from command to log file immediately */ + log_file.write(&ds_res); + log_file.flush(); + dynstr_set(&ds_res, 0); + } + + log_file.close(); + + start_lineno= 0; + verbose_msg("... Done processing test commands."); + + if (parsing_disabled) + die("Test ended with parsing disabled"); + + /* + The whole test has been executed _successfully_. + Time to compare result or save it to record file. + The entire output from test is in the log file + */ + if (log_file.bytes_written()) + { + if (result_file_name) + { + /* A result file has been specified */ + + if (record) + { + /* Recording */ + + /* save a copy of the log to result file */ + if (my_copy(log_file.file_name(), result_file_name, MYF(0)) != 0) + die("Failed to copy '%s' to '%s', errno: %d", + log_file.file_name(), result_file_name, errno); + + } + else + { + /* Check that the output from test is equal to result file */ + check_result(); + } + } + } + else + { + /* Empty output is an error *unless* we also have an empty result file */ + if (! result_file_name || record || + compare_files (log_file.file_name(), result_file_name)) + { + die("The test didn't produce any output"); + } + else + { + empty_result= TRUE; /* Meaning empty was expected */ + } + } + + if (!command_executed && result_file_name && !empty_result) + die("No queries executed but non-empty result file found!"); + + verbose_msg("Test has succeeded!"); + timer_output(); + /* Yes, if we got this far the test has succeeded! Sakila smiles */ + cleanup_and_exit(0); + return 0; /* Keep compiler happy too */ +} + + +/* + A primitive timer that give results in milliseconds if the + --timer-file=<filename> is given. The timer result is written + to that file when the result is available. To not confuse + mysql-test-run with an old obsolete result, we remove the file + before executing any commands. The time we measure is + + - If no explicit 'start_timer' or 'end_timer' is given in the + test case, the timer measure how long we execute in mysqltest. + + - If only 'start_timer' is given we measure how long we execute + from that point until we terminate mysqltest. + + - If only 'end_timer' is given we measure how long we execute + from that we enter mysqltest to the 'end_timer' is command is + executed. + + - If both 'start_timer' and 'end_timer' are given we measure + the time between executing the two commands. +*/ + +void timer_output(void) +{ + if (timer_file) + { + char buf[32], *end; + ulonglong timer= timer_now() - timer_start; + end= longlong10_to_str(timer, buf, 10); + str_to_file(timer_file,buf, (int) (end-buf)); + /* Timer has been written to the file, don't use it anymore */ + timer_file= 0; + } +} + + +ulonglong timer_now(void) +{ + return my_interval_timer() / 1000000; +} + + +/* + Get arguments for replace_columns. The syntax is: + replace-column column_number to_string [column_number to_string ...] + Where each argument may be quoted with ' or " + A argument may also be a variable, in which case the value of the + variable is replaced. +*/ + +void do_get_replace_column(struct st_command *command) +{ + char *from= command->first_argument; + char *buff, *start; + DBUG_ENTER("get_replace_columns"); + + free_replace_column(); + if (!*from) + die("Missing argument in %s", command->query); + + /* Allocate a buffer for results */ + start= buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, strlen(from)+1, + MYF(MY_WME|MY_FAE)); + while (*from) + { + char *to; + uint column_number; + to= get_string(&buff, &from, command); + if (!(column_number= atoi(to)) || column_number > MAX_COLUMNS) + die("Wrong column number to replace_column in '%s'", command->query); + if (!*from) + die("Wrong number of arguments to replace_column in '%s'", + command->query); + to= get_string(&buff, &from, command); + my_free(replace_column[column_number-1]); + replace_column[column_number-1]= my_strdup(PSI_NOT_INSTRUMENTED, to, + MYF(MY_WME|MY_FAE)); + set_if_bigger(max_replace_column, column_number); + } + my_free(start); + command->last_argument= command->end; + + DBUG_VOID_RETURN; +} + + +void free_replace_column() +{ + uint i; + for (i=0 ; i < max_replace_column ; i++) + { + if (replace_column[i]) + { + my_free(replace_column[i]); + replace_column[i]= 0; + } + } + max_replace_column= 0; +} + + +/****************************************************************************/ +/* + Replace functions +*/ + +/* Definitions for replace result */ + +typedef struct st_pointer_array { /* when using array-strings */ + TYPELIB typelib; /* Pointer to strings */ + uchar *str; /* Strings is here */ + uint8 *flag; /* Flag about each var. */ + uint array_allocs,max_count,length,max_length; +} POINTER_ARRAY; + +struct st_replace *init_replace(char * *from, char * *to, uint count, + char * word_end_chars); +int insert_pointer_name(POINTER_ARRAY *pa,char * name); +void free_pointer_array(POINTER_ARRAY *pa); + +/* + Get arguments for replace. The syntax is: + replace from to [from to ...] + Where each argument may be quoted with ' or " + A argument may also be a variable, in which case the value of the + variable is replaced. +*/ + +void do_get_replace(struct st_command *command) +{ + uint i; + char *from= command->first_argument; + char *buff, *start; + char word_end_chars[256], *pos; + POINTER_ARRAY to_array, from_array; + DBUG_ENTER("do_get_replace"); + + free_replace(); + + bzero(&to_array,sizeof(to_array)); + bzero(&from_array,sizeof(from_array)); + if (!*from) + die("Missing argument in %s", command->query); + start= buff= (char*)my_malloc(PSI_NOT_INSTRUMENTED, strlen(from)+1, + MYF(MY_WME|MY_FAE)); + while (*from) + { + char *to= buff; + to= get_string(&buff, &from, command); + if (!*from) + die("Wrong number of arguments to replace_result in '%s'", + command->query); + fix_win_paths(to, from - to); + insert_pointer_name(&from_array,to); + to= get_string(&buff, &from, command); + insert_pointer_name(&to_array,to); + } + for (i= 1,pos= word_end_chars ; i < 256 ; i++) + if (my_isspace(charset_info,i)) + *pos++= i; + *pos=0; /* End pointer */ + if (!(glob_replace= init_replace((char**) from_array.typelib.type_names, + (char**) to_array.typelib.type_names, + (uint) from_array.typelib.count, + word_end_chars))) + die("Can't initialize replace from '%s'", command->query); + free_pointer_array(&from_array); + free_pointer_array(&to_array); + my_free(start); + command->last_argument= command->end; + DBUG_VOID_RETURN; +} + + +void free_replace() +{ + DBUG_ENTER("free_replace"); + my_free(glob_replace); + glob_replace= NULL; + DBUG_VOID_RETURN; +} + + +typedef struct st_replace { + int found; + struct st_replace *next[256]; +} REPLACE; + +typedef struct st_replace_found { + int found; + uint to_offset; + int from_offset; + char *replace_string; +} REPLACE_STRING; + + +void replace_strings_append(REPLACE *rep, DYNAMIC_STRING* ds, + const char *str) +{ + REPLACE *rep_pos; + REPLACE_STRING *rep_str; + const char *start, *from; + DBUG_ENTER("replace_strings_append"); + + start= from= str; + rep_pos=rep+1; + for (;;) + { + /* Loop through states */ + DBUG_PRINT("info", ("Looping through states")); + while (!rep_pos->found) + rep_pos= rep_pos->next[(uchar) *from++]; + + /* Does this state contain a string to be replaced */ + if (!(rep_str = ((REPLACE_STRING*) rep_pos))->replace_string) + { + /* No match found */ + dynstr_append_mem(ds, start, from - start - 1); + DBUG_PRINT("exit", ("Found no more string to replace, appended: %s", start)); + DBUG_VOID_RETURN; + } + + /* Found a string that needs to be replaced */ + DBUG_PRINT("info", ("found: %d, to_offset: %u, from_offset: %d, string: %s", + rep_str->found, rep_str->to_offset, + rep_str->from_offset, rep_str->replace_string)); + + /* Append part of original string before replace string */ + dynstr_append_mem(ds, start, (from - rep_str->to_offset) - start); + + /* Append replace string */ + dynstr_append_mem(ds, rep_str->replace_string, + strlen(rep_str->replace_string)); + + if (!*(from-=rep_str->from_offset) && rep_pos->found != 2) + { + /* End of from string */ + DBUG_PRINT("exit", ("Found end of from string")); + DBUG_VOID_RETURN; + } + start= from; + rep_pos=rep; + } +} + + +/* + Regex replace functions +*/ + + +/* Stores regex substitutions */ + +struct st_regex +{ + char* pattern; /* Pattern to be replaced */ + char* replace; /* String or expression to replace the pattern with */ + int icase; /* true if the match is case insensitive */ +}; + +int reg_replace(char** buf_p, int* buf_len_p, char *pattern, char *replace, + char *string, int icase); + +bool parse_re_part(char *start_re, char *end_re, + char **p, char *end, char **buf) +{ + if (*start_re != *end_re) + { + switch ((*start_re= *(*p)++)) { + case '(': *end_re= ')'; break; + case '[': *end_re= ']'; break; + case '{': *end_re= '}'; break; + case '<': *end_re= '>'; break; + default: *end_re= *start_re; + } + } + + while (*p < end && **p != *end_re) + { + if ((*p)[0] == '\\' && *p + 1 < end && (*p)[1] == *end_re) + (*p)++; + + *(*buf)++= *(*p)++; + } + *(*buf)++= 0; + + (*p)++; + + return *p > end; +} + +/* + Initializes the regular substitution expression to be used in the + result output of test. + + Returns: st_replace_regex struct with pairs of substitutions +*/ +void append_replace_regex(char*, char*, struct st_replace_regex*, char**); + +struct st_replace_regex* init_replace_regex(char* expr) +{ + char *expr_end, *buf_p; + struct st_replace_regex* res; + size_t expr_len= strlen(expr); + + /* my_malloc() will die on fail with MY_FAE */ + res=(struct st_replace_regex*)my_malloc(PSI_NOT_INSTRUMENTED, + sizeof(*res)+8192 ,MYF(MY_FAE+MY_WME)); + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &res->regex_arr, sizeof(struct st_regex), 128, 128, MYF(0)); + + expr_end= expr + expr_len; + buf_p= (char*)res + sizeof(*res); + append_replace_regex(expr, expr_end, res, &buf_p); + + res->odd_buf_len= res->even_buf_len= 8192; + res->even_buf= (char*)my_malloc(PSI_NOT_INSTRUMENTED, res->even_buf_len,MYF(MY_WME+MY_FAE)); + res->odd_buf= (char*)my_malloc(PSI_NOT_INSTRUMENTED, res->odd_buf_len,MYF(MY_WME+MY_FAE)); + res->buf= res->even_buf; + + return res; +} + + +void append_replace_regex(char* expr, char *expr_end, struct st_replace_regex* res, + char **buf_p) +{ + char* p, start_re, end_re= 1; + struct st_regex reg; + + p= expr; + + /* for each regexp substitution statement */ + while (p < expr_end) + { + bzero(®,sizeof(reg)); + /* find the start of the statement */ + while (my_isspace(charset_info, *p) && p < expr_end) + p++; + + if (p >= expr_end) + { + if (res->regex_arr.elements) + break; + else + goto err; + } + + start_re= 0; + reg.pattern= *buf_p; + + /* Allow variable for the *entire* list of replacements */ + if (*p == '$') + { + const char *v_end= 0; + VAR *val= var_get(p, &v_end, 0, 1); + + if (val) + { + char *expr, *expr_end; + expr= val->str_val; + expr_end= expr + val->str_val_len; + append_replace_regex(expr, expr_end, res, buf_p); + } + + p= (char *) v_end + 1; + continue; + } + else + { + if (parse_re_part(&start_re, &end_re, &p, expr_end, buf_p)) + goto err; + + reg.replace= *buf_p; + if (parse_re_part(&start_re, &end_re, &p, expr_end, buf_p)) + goto err; + } + + /* Check if we should do matching case insensitive */ + if (p < expr_end && *p == 'i') + { + p++; + reg.icase= 1; + } + + /* done parsing the statement, now place it in regex_arr */ + if (insert_dynamic(&res->regex_arr, ®)) + die("Out of memory"); + } + + return; + +err: + my_free(res->regex_arr.buffer); + my_free(res); + die("Error parsing replace_regex \"%s\"", expr); +} + +/* + Execute all substitutions on val. + + Returns: true if substitution was made, false otherwise + Side-effect: Sets r->buf to be the buffer with all substitutions done. + + IN: + struct st_replace_regex* r + char* val + Out: + struct st_replace_regex* r + r->buf points at the resulting buffer + r->even_buf and r->odd_buf might have been reallocated + r->even_buf_len and r->odd_buf_len might have been changed + + TODO: at some point figure out if there is a way to do everything + in one pass +*/ + +int multi_reg_replace(struct st_replace_regex* r,char* val) +{ + uint i; + char* in_buf, *out_buf; + int* buf_len_p; + + in_buf= val; + out_buf= r->even_buf; + buf_len_p= &r->even_buf_len; + r->buf= 0; + + /* For each substitution, do the replace */ + for (i= 0; i < r->regex_arr.elements; i++) + { + struct st_regex re; + char* save_out_buf= out_buf; + + get_dynamic(&r->regex_arr, &re, i); + + if (!reg_replace(&out_buf, buf_len_p, re.pattern, re.replace, + in_buf, re.icase)) + { + /* if the buffer has been reallocated, make adjustments */ + if (save_out_buf != out_buf) + { + if (save_out_buf == r->even_buf) + r->even_buf= out_buf; + else + r->odd_buf= out_buf; + } + + r->buf= out_buf; + if (in_buf == val) + in_buf= r->odd_buf; + + swap_variables(char*,in_buf,out_buf); + + buf_len_p= (out_buf == r->even_buf) ? &r->even_buf_len : + &r->odd_buf_len; + } + } + + return (r->buf == 0); +} + +/* + Parse the regular expression to be used in all result files + from now on. + + The syntax is --replace_regex /from/to/i /from/to/i ... + i means case-insensitive match. If omitted, the match is + case-sensitive + +*/ +void do_get_replace_regex(struct st_command *command) +{ + char *expr= command->first_argument; + free_replace_regex(); + if (expr && *expr && !(glob_replace_regex=init_replace_regex(expr))) + die("Could not init replace_regex"); + command->last_argument= command->end; +} + +void free_replace_regex() +{ + if (glob_replace_regex) + { + delete_dynamic(&glob_replace_regex->regex_arr); + my_free(glob_replace_regex->even_buf); + my_free(glob_replace_regex->odd_buf); + my_free(glob_replace_regex); + glob_replace_regex=0; + } +} + + + +/* + auxiliary macro used by reg_replace + makes sure the result buffer has sufficient length +*/ +#define SECURE_REG_BUF if (buf_len < need_buf_len) \ + { \ + ssize_t off= res_p - buf; \ + buf= (char*)my_realloc(PSI_NOT_INSTRUMENTED, buf,need_buf_len,MYF(MY_WME+MY_FAE)); \ + res_p= buf + off; \ + buf_len= need_buf_len; \ + } \ + \ +/* + Performs a regex substitution + + IN: + + buf_p - result buffer pointer. Will change if reallocated + buf_len_p - result buffer length. Will change if the buffer is reallocated + pattern - regexp pattern to match + replace - replacement expression + string - the string to perform substitutions in + icase - flag, if set to 1 the match is case insensitive +*/ +int reg_replace(char** buf_p, int* buf_len_p, char *pattern, + char *replace, char *string, int icase) +{ + regex_t r; + regmatch_t *subs; + char *replace_end; + char *buf= *buf_p; + size_t len; + size_t buf_len, need_buf_len; + int cflags= REG_EXTENDED | REG_DOTALL; + int err_code; + char *res_p,*str_p,*str_end; + + DBUG_ASSERT(*buf_len_p > 0); + + buf_len= (size_t)*buf_len_p; + len= strlen(string); + str_end= string + len; + + /* start with a buffer of a reasonable size that hopefully will not + need to be reallocated + */ + need_buf_len= len * 2 + 1; + res_p= buf; + + SECURE_REG_BUF + + if (icase) + cflags|= REG_ICASE; + + if ((err_code= regcomp(&r,pattern,cflags))) + { + check_regerr(&r,err_code); + return 1; + } + + subs= (regmatch_t*)my_malloc(PSI_NOT_INSTRUMENTED, sizeof(regmatch_t) * (r.re_nsub+1), + MYF(MY_WME+MY_FAE)); + + *res_p= 0; + str_p= string; + replace_end= replace + strlen(replace); + + /* for each pattern match instance perform a replacement */ + while (!err_code) + { + /* find the match */ + err_code= regexec(&r,str_p, r.re_nsub+1, subs, + (str_p == string) ? 0 : REG_NOTBOL); + + /* if regular expression error (eg. bad syntax, or out of memory) */ + if (err_code && err_code != REG_NOMATCH) + { + check_regerr(&r,err_code); + regfree(&r); + return 1; + } + + /* if match found */ + if (!err_code) + { + char* expr_p= replace; + int c; + + /* + we need at least what we have so far in the buffer + the part + before this match + */ + need_buf_len= (res_p - buf) + (int) subs[0].rm_so; + + /* on this pass, calculate the memory for the result buffer */ + while (expr_p < replace_end) + { + int back_ref_num= -1; + c= *expr_p; + + if (c == '\\' && expr_p + 1 < replace_end) + { + back_ref_num= (int) (expr_p[1] - '0'); + } + + /* found a valid back_ref (eg. \1)*/ + if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) + { + regoff_t start_off, end_off; + if ((start_off=subs[back_ref_num].rm_so) > -1 && + (end_off=subs[back_ref_num].rm_eo) > -1) + { + need_buf_len += (int) (end_off - start_off); + } + expr_p += 2; + } + else + { + expr_p++; + need_buf_len++; + } + } + need_buf_len++; + /* + now that we know the size of the buffer, + make sure it is big enough + */ + SECURE_REG_BUF + + /* copy the pre-match part */ + if (subs[0].rm_so) + { + memcpy(res_p, str_p, (size_t) subs[0].rm_so); + res_p+= subs[0].rm_so; + } + + expr_p= replace; + + /* copy the match and expand back_refs */ + while (expr_p < replace_end) + { + int back_ref_num= -1; + c= *expr_p; + + if (c == '\\' && expr_p + 1 < replace_end) + { + back_ref_num= expr_p[1] - '0'; + } + + if (back_ref_num >= 0 && back_ref_num <= (int)r.re_nsub) + { + regoff_t start_off, end_off; + if ((start_off=subs[back_ref_num].rm_so) > -1 && + (end_off=subs[back_ref_num].rm_eo) > -1) + { + int block_len= (int) (end_off - start_off); + memcpy(res_p,str_p + start_off, block_len); + res_p += block_len; + } + expr_p += 2; + } + else + { + *res_p++ = *expr_p++; + } + } + + /* handle the post-match part */ + if (subs[0].rm_so == subs[0].rm_eo) + { + if (str_p + subs[0].rm_so >= str_end) + break; + str_p += subs[0].rm_eo ; + *res_p++ = *str_p++; + } + else + { + str_p += subs[0].rm_eo; + } + } + else /* no match this time, just copy the string as is */ + { + size_t left_in_str= str_end-str_p; + need_buf_len= (res_p-buf) + left_in_str; + SECURE_REG_BUF + memcpy(res_p,str_p,left_in_str); + res_p += left_in_str; + str_p= str_end; + } + } + my_free(subs); + regfree(&r); + *res_p= 0; + *buf_p= buf; + *buf_len_p= (int)buf_len; + return 0; +} + + +#ifndef WORD_BIT +#define WORD_BIT (8*sizeof(uint)) +#endif + +#define SET_MALLOC_HUNC 64 +#define LAST_CHAR_CODE 259 + +typedef struct st_rep_set { + uint *bits; /* Pointer to used sets */ + short next[LAST_CHAR_CODE]; /* Pointer to next sets */ + uint found_len; /* Best match to date */ + int found_offset; + uint table_offset; + uint size_of_bits; /* For convenience */ +} REP_SET; + +typedef struct st_rep_sets { + uint count; /* Number of sets */ + uint extra; /* Extra sets in buffer */ + uint invisible; /* Sets not chown */ + uint size_of_bits; + REP_SET *set,*set_buffer; + uint *bit_buffer; +} REP_SETS; + +typedef struct st_found_set { + uint table_offset; + int found_offset; +} FOUND_SET; + +typedef struct st_follow { + int chr; + uint table_offset; + uint len; +} FOLLOWS; + + +int init_sets(REP_SETS *sets,uint states); +REP_SET *make_new_set(REP_SETS *sets); +void make_sets_invisible(REP_SETS *sets); +void free_last_set(REP_SETS *sets); +void free_sets(REP_SETS *sets); +void internal_set_bit(REP_SET *set, uint bit); +void internal_clear_bit(REP_SET *set, uint bit); +void or_bits(REP_SET *to,REP_SET *from); +void copy_bits(REP_SET *to,REP_SET *from); +int cmp_bits(REP_SET *set1,REP_SET *set2); +int get_next_bit(REP_SET *set,uint lastpos); +int find_set(REP_SETS *sets,REP_SET *find); +int find_found(FOUND_SET *found_set,uint table_offset, + int found_offset); +uint start_at_word(char * pos); +uint end_of_word(char * pos); + +static uint found_sets=0; + + +uint replace_len(char * str) +{ + uint len=0; + while (*str) + { + str++; + len++; + } + return len; +} + +/* Init a replace structure for further calls */ + +REPLACE *init_replace(char * *from, char * *to,uint count, + char * word_end_chars) +{ + static const int SPACE_CHAR= 256; + static const int END_OF_LINE= 258; + + uint i,j,states,set_nr,len,result_len,max_length,found_end,bits_set,bit_nr; + int used_sets,chr,default_state; + char used_chars[LAST_CHAR_CODE],is_word_end[256]; + char * pos, *to_pos, **to_array; + REP_SETS sets; + REP_SET *set,*start_states,*word_states,*new_set; + FOLLOWS *follow,*follow_ptr; + REPLACE *replace; + FOUND_SET *found_set; + REPLACE_STRING *rep_str; + DBUG_ENTER("init_replace"); + + /* Count number of states */ + for (i=result_len=max_length=0 , states=2 ; i < count ; i++) + { + len=replace_len(from[i]); + if (!len) + { + errno=EINVAL; + DBUG_RETURN(0); + } + states+=len+1; + result_len+=(uint) strlen(to[i])+1; + if (len > max_length) + max_length=len; + } + bzero(is_word_end, sizeof(is_word_end)); + for (i=0 ; word_end_chars[i] ; i++) + is_word_end[(uchar) word_end_chars[i]]=1; + + if (init_sets(&sets,states)) + DBUG_RETURN(0); + found_sets=0; + if (!(found_set= (FOUND_SET*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(FOUND_SET)*max_length*count, + MYF(MY_WME|MY_FAE)))) + { + free_sets(&sets); + DBUG_RETURN(0); + } + (void) make_new_set(&sets); /* Set starting set */ + make_sets_invisible(&sets); /* Hide previous sets */ + used_sets=-1; + word_states=make_new_set(&sets); /* Start of new word */ + start_states=make_new_set(&sets); /* This is first state */ + if (!(follow=(FOLLOWS*) my_malloc(PSI_NOT_INSTRUMENTED, (states+2)*sizeof(FOLLOWS),MYF(MY_WME|MY_FAE)))) + { + free_sets(&sets); + my_free(found_set); + DBUG_RETURN(0); + } + + /* Init follow_ptr[] */ + for (i=0, states=1, follow_ptr=follow+1 ; i < count ; i++) + { + if (from[i][0] == '\\' && from[i][1] == '^') + { + internal_set_bit(start_states,states+1); + if (!from[i][2]) + { + start_states->table_offset=i; + start_states->found_offset=1; + } + } + else if (from[i][0] == '\\' && from[i][1] == '$') + { + internal_set_bit(start_states,states); + internal_set_bit(word_states,states); + if (!from[i][2] && start_states->table_offset == (uint) ~0) + { + start_states->table_offset=i; + start_states->found_offset=0; + } + } + else + { + internal_set_bit(word_states,states); + if (from[i][0] == '\\' && (from[i][1] == 'b' && from[i][2])) + internal_set_bit(start_states,states+1); + else + internal_set_bit(start_states,states); + } + for (pos=from[i], len=0; *pos ; pos++) + { + follow_ptr->chr= (uchar) *pos; + follow_ptr->table_offset=i; + follow_ptr->len= ++len; + follow_ptr++; + } + follow_ptr->chr=0; + follow_ptr->table_offset=i; + follow_ptr->len=len; + follow_ptr++; + states+=(uint) len+1; + } + + + for (set_nr=0,pos=0 ; set_nr < sets.count ; set_nr++) + { + set=sets.set+set_nr; + default_state= 0; /* Start from beginning */ + + /* If end of found-string not found or start-set with current set */ + + for (i= (uint) ~0; (i=get_next_bit(set,i)) ;) + { + if (!follow[i].chr) + { + if (! default_state) + default_state= find_found(found_set,set->table_offset, + set->found_offset+1); + } + } + copy_bits(sets.set+used_sets,set); /* Save set for changes */ + if (!default_state) + or_bits(sets.set+used_sets,sets.set); /* Can restart from start */ + + /* Find all chars that follows current sets */ + bzero(used_chars, sizeof(used_chars)); + for (i= (uint) ~0; (i=get_next_bit(sets.set+used_sets,i)) ;) + { + used_chars[follow[i].chr]=1; + if ((follow[i].chr == SPACE_CHAR && !follow[i+1].chr && + follow[i].len > 1) || follow[i].chr == END_OF_LINE) + used_chars[0]=1; + } + + /* Mark word_chars used if \b is in state */ + if (used_chars[SPACE_CHAR]) + for (pos= word_end_chars ; *pos ; pos++) + used_chars[(int) (uchar) *pos] = 1; + + /* Handle other used characters */ + for (chr= 0 ; chr < 256 ; chr++) + { + if (! used_chars[chr]) + set->next[chr]= chr ? default_state : -1; + else + { + new_set=make_new_set(&sets); + set=sets.set+set_nr; /* if realloc */ + new_set->table_offset=set->table_offset; + new_set->found_len=set->found_len; + new_set->found_offset=set->found_offset+1; + found_end=0; + + for (i= (uint) ~0 ; (i=get_next_bit(sets.set+used_sets,i)) ; ) + { + if (!follow[i].chr || follow[i].chr == chr || + (follow[i].chr == SPACE_CHAR && + (is_word_end[chr] || + (!chr && follow[i].len > 1 && ! follow[i+1].chr))) || + (follow[i].chr == END_OF_LINE && ! chr)) + { + if ((! chr || (follow[i].chr && !follow[i+1].chr)) && + follow[i].len > found_end) + found_end=follow[i].len; + if (chr && follow[i].chr) + internal_set_bit(new_set,i+1); /* To next set */ + else + internal_set_bit(new_set,i); + } + } + if (found_end) + { + new_set->found_len=0; /* Set for testing if first */ + bits_set=0; + for (i= (uint) ~0; (i=get_next_bit(new_set,i)) ;) + { + if ((follow[i].chr == SPACE_CHAR || + follow[i].chr == END_OF_LINE) && ! chr) + bit_nr=i+1; + else + bit_nr=i; + if (follow[bit_nr-1].len < found_end || + (new_set->found_len && + (chr == 0 || !follow[bit_nr].chr))) + internal_clear_bit(new_set,i); + else + { + if (chr == 0 || !follow[bit_nr].chr) + { /* best match */ + new_set->table_offset=follow[bit_nr].table_offset; + if (chr || (follow[i].chr == SPACE_CHAR || + follow[i].chr == END_OF_LINE)) + new_set->found_offset=found_end; /* New match */ + new_set->found_len=found_end; + } + bits_set++; + } + } + if (bits_set == 1) + { + set->next[chr] = find_found(found_set, + new_set->table_offset, + new_set->found_offset); + free_last_set(&sets); + } + else + set->next[chr] = find_set(&sets,new_set); + } + else + set->next[chr] = find_set(&sets,new_set); + } + } + } + + /* Alloc replace structure for the replace-state-machine */ + + if ((replace=(REPLACE*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(REPLACE)*(sets.count)+ + sizeof(REPLACE_STRING)*(found_sets+1)+ + sizeof(char *)*count+result_len, + MYF(MY_WME|MY_FAE|MY_ZEROFILL)))) + { + rep_str=(REPLACE_STRING*) (replace+sets.count); + to_array= (char **) (rep_str+found_sets+1); + to_pos=(char *) (to_array+count); + for (i=0 ; i < count ; i++) + { + to_array[i]=to_pos; + to_pos=strmov(to_pos,to[i])+1; + } + rep_str[0].found=1; + rep_str[0].replace_string=0; + for (i=1 ; i <= found_sets ; i++) + { + pos=from[found_set[i-1].table_offset]; + rep_str[i].found= !strncmp(pos, "\\^", 3) ? 2 : 1; + rep_str[i].replace_string=to_array[found_set[i-1].table_offset]; + rep_str[i].to_offset=found_set[i-1].found_offset-start_at_word(pos); + rep_str[i].from_offset=found_set[i-1].found_offset-replace_len(pos)+ + end_of_word(pos); + } + for (i=0 ; i < sets.count ; i++) + { + for (j=0 ; j < 256 ; j++) + if (sets.set[i].next[j] >= 0) + replace[i].next[j]=replace+sets.set[i].next[j]; + else + replace[i].next[j]=(REPLACE*) (rep_str+(-sets.set[i].next[j]-1)); + } + } + my_free(follow); + free_sets(&sets); + my_free(found_set); + DBUG_PRINT("exit",("Replace table has %d states",sets.count)); + DBUG_RETURN(replace); +} + + +int init_sets(REP_SETS *sets,uint states) +{ + bzero(sets, sizeof(*sets)); + sets->size_of_bits=((states+7)/8); + if (!(sets->set_buffer=(REP_SET*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(REP_SET)*SET_MALLOC_HUNC, + MYF(MY_WME|MY_FAE)))) + return 1; + if (!(sets->bit_buffer=(uint*) my_malloc(PSI_NOT_INSTRUMENTED, sizeof(uint)*sets->size_of_bits* + SET_MALLOC_HUNC,MYF(MY_WME|MY_FAE)))) + { + my_free(sets->set); + return 1; + } + return 0; +} + +/* Make help sets invisible for nicer coding */ + +void make_sets_invisible(REP_SETS *sets) +{ + sets->invisible=sets->count; + sets->set+=sets->count; + sets->count=0; +} + +REP_SET *make_new_set(REP_SETS *sets) +{ + uint i,count,*bit_buffer; + REP_SET *set; + if (sets->extra) + { + sets->extra--; + set=sets->set+ sets->count++; + bzero(set->bits, sizeof(uint) * sets->size_of_bits); + bzero(&set->next[0], sizeof(set->next[0]) * LAST_CHAR_CODE); + set->found_offset=0; + set->found_len=0; + set->table_offset= (uint) ~0; + set->size_of_bits=sets->size_of_bits; + return set; + } + count=sets->count+sets->invisible+SET_MALLOC_HUNC; + if (!(set=(REP_SET*) my_realloc(PSI_NOT_INSTRUMENTED, sets->set_buffer, sizeof(REP_SET)*count, + MYF(MY_WME)))) + return 0; + sets->set_buffer=set; + sets->set=set+sets->invisible; + if (!(bit_buffer=(uint*) my_realloc(PSI_NOT_INSTRUMENTED, sets->bit_buffer, + (sizeof(uint)*sets->size_of_bits)*count, + MYF(MY_WME)))) + return 0; + sets->bit_buffer=bit_buffer; + for (i=0 ; i < count ; i++) + { + sets->set_buffer[i].bits=bit_buffer; + bit_buffer+=sets->size_of_bits; + } + sets->extra=SET_MALLOC_HUNC; + return make_new_set(sets); +} + +void free_last_set(REP_SETS *sets) +{ + sets->count--; + sets->extra++; + return; +} + +void free_sets(REP_SETS *sets) +{ + my_free(sets->set_buffer); + my_free(sets->bit_buffer); + return; +} + +void internal_set_bit(REP_SET *set, uint bit) +{ + set->bits[bit / WORD_BIT] |= 1 << (bit % WORD_BIT); + return; +} + +void internal_clear_bit(REP_SET *set, uint bit) +{ + set->bits[bit / WORD_BIT] &= ~ (1 << (bit % WORD_BIT)); + return; +} + + +void or_bits(REP_SET *to,REP_SET *from) +{ + uint i; + for (i=0 ; i < to->size_of_bits ; i++) + to->bits[i]|=from->bits[i]; + return; +} + +void copy_bits(REP_SET *to,REP_SET *from) +{ + memcpy(to->bits, from->bits, + (size_t) (sizeof(uint) * to->size_of_bits)); +} + +int cmp_bits(REP_SET *set1,REP_SET *set2) +{ + return memcmp(set1->bits, set2->bits, + sizeof(uint) * set1->size_of_bits); +} + + +/* Get next set bit from set. */ + +int get_next_bit(REP_SET *set,uint lastpos) +{ + uint pos,*start,*end,bits; + + start=set->bits+ ((lastpos+1) / WORD_BIT); + end=set->bits + set->size_of_bits; + bits=start[0] & ~((1U << ((lastpos+1) % WORD_BIT)) -1); + + while (! bits && ++start < end) + bits=start[0]; + if (!bits) + return 0; + pos=(uint) (start-set->bits)*WORD_BIT; + while (! (bits & 1)) + { + bits>>=1; + pos++; + } + return pos; +} + +/* find if there is a same set in sets. If there is, use it and + free given set, else put in given set in sets and return its + position */ + +int find_set(REP_SETS *sets,REP_SET *find) +{ + uint i; + for (i=0 ; i < sets->count-1 ; i++) + { + if (!cmp_bits(sets->set+i,find)) + { + free_last_set(sets); + return i; + } + } + return i; /* return new position */ +} + +/* find if there is a found_set with same table_offset & found_offset + If there is return offset to it, else add new offset and return pos. + Pos returned is -offset-2 in found_set_structure because it is + saved in set->next and set->next[] >= 0 points to next set and + set->next[] == -1 is reserved for end without replaces. +*/ + +int find_found(FOUND_SET *found_set,uint table_offset, int found_offset) +{ + int i; + for (i=0 ; (uint) i < found_sets ; i++) + if (found_set[i].table_offset == table_offset && + found_set[i].found_offset == found_offset) + return -i-2; + found_set[i].table_offset=table_offset; + found_set[i].found_offset=found_offset; + found_sets++; + return -i-2; /* return new position */ +} + +/* Return 1 if regexp starts with \b or ends with \b*/ + +uint start_at_word(char * pos) +{ + return (((!memcmp(pos, "\\b",2) && pos[2]) || + !memcmp(pos, "\\^", 2)) ? 1 : 0); +} + +uint end_of_word(char * pos) +{ + char * end=strend(pos); + return ((end > pos+2 && !memcmp(end-2, "\\b", 2)) || + (end >= pos+2 && !memcmp(end-2, "\\$",2))) ? 1 : 0; +} + +/**************************************************************************** + * Handle replacement of strings + ****************************************************************************/ + +#define PC_MALLOC 256 /* Bytes for pointers */ +#define PS_MALLOC 512 /* Bytes for data */ + +int insert_pointer_name(POINTER_ARRAY *pa,char * name) +{ + uint i,length,old_count; + uchar *new_pos; + const char **new_array; + DBUG_ENTER("insert_pointer_name"); + + if (! pa->typelib.count) + { + if (!(pa->typelib.type_names=(const char **) + my_malloc(PSI_NOT_INSTRUMENTED, ((PC_MALLOC-MALLOC_OVERHEAD)/ + (sizeof(char *)+sizeof(*pa->flag))* + (sizeof(char *)+sizeof(*pa->flag))),MYF(MY_WME|MY_FAE)))) + DBUG_RETURN(-1); + if (!(pa->str= (uchar*) my_malloc(PSI_NOT_INSTRUMENTED, PS_MALLOC - MALLOC_OVERHEAD, + MYF(MY_WME|MY_FAE)))) + { + my_free(pa->typelib.type_names); + DBUG_RETURN (-1); + } + pa->max_count=(PC_MALLOC-MALLOC_OVERHEAD)/(sizeof(uchar*)+ + sizeof(*pa->flag)); + pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count); + pa->length=0; + pa->max_length=PS_MALLOC-MALLOC_OVERHEAD; + pa->array_allocs=1; + } + length=(uint) strlen(name)+1; + if (pa->length+length >= pa->max_length) + { + if (!(new_pos= (uchar*) my_realloc(PSI_NOT_INSTRUMENTED, pa->str, pa->length + length + PS_MALLOC, + MYF(MY_WME)))) + DBUG_RETURN(1); + if (new_pos != pa->str) + { + my_ptrdiff_t diff=PTR_BYTE_DIFF(new_pos,pa->str); + for (i=0 ; i < pa->typelib.count ; i++) + pa->typelib.type_names[i]= ADD_TO_PTR(pa->typelib.type_names[i],diff, + char*); + pa->str=new_pos; + } + pa->max_length= pa->length+length+PS_MALLOC; + } + if (pa->typelib.count >= pa->max_count-1) + { + int len; + pa->array_allocs++; + len=(PC_MALLOC*pa->array_allocs - MALLOC_OVERHEAD); + if (!(new_array=(const char **) my_realloc(PSI_NOT_INSTRUMENTED, pa->typelib.type_names, + len/ + (sizeof(uchar*)+sizeof(*pa->flag))* + (sizeof(uchar*)+sizeof(*pa->flag)), + MYF(MY_WME)))) + DBUG_RETURN(1); + pa->typelib.type_names=new_array; + old_count=pa->max_count; + pa->max_count=len/(sizeof(uchar*) + sizeof(*pa->flag)); + pa->flag= (uint8*) (pa->typelib.type_names+pa->max_count); + memcpy(pa->flag, (pa->typelib.type_names +old_count), + old_count*sizeof(*pa->flag)); + } + pa->flag[pa->typelib.count]=0; /* Reset flag */ + pa->typelib.type_names[pa->typelib.count++]= (char*) pa->str+pa->length; + pa->typelib.type_names[pa->typelib.count]= NullS; /* Put end-mark */ + (void) strmov((char*) pa->str + pa->length,name); + pa->length+=length; + DBUG_RETURN(0); +} /* insert_pointer_name */ + + +/* free pointer array */ + +void free_pointer_array(POINTER_ARRAY *pa) +{ + if (pa->typelib.count) + { + pa->typelib.count=0; + my_free(pa->typelib.type_names); + pa->typelib.type_names=0; + my_free(pa->str); + } +} /* free_pointer_array */ + + +/* Functions that uses replace and replace_regex */ + +/* Append the string to ds, with optional replace */ +void replace_dynstr_append_mem(DYNAMIC_STRING *ds, const char *val, size_t len) +{ + char lower[1024]; + + if (len < sizeof(lower) - 1) + { + if (display_result_lower) + { + /* Convert to lower case, and do this first */ + char *c= lower; + for (const char *v= val, *end_v= v + len; v < end_v; v++) + *c++= my_tolower(charset_info, *v); + *c= '\0'; + /* Copy from this buffer instead */ + } + else + { + memcpy(lower, val, len); + lower[len]= 0; + } + fix_win_paths(lower, len); + val= lower; + } + + if (glob_replace_regex) + { + /* Regex replace */ + if (!multi_reg_replace(glob_replace_regex, (char*)val)) + { + val= glob_replace_regex->buf; + len= strlen(val); + } + } + + if (glob_replace) + { + /* Normal replace */ + replace_strings_append(glob_replace, ds, val); + } + else + dynstr_append_mem(ds, val, len); +} + + +/* Append zero-terminated string to ds, with optional replace */ +void replace_dynstr_append(DYNAMIC_STRING *ds, const char *val) +{ + replace_dynstr_append_mem(ds, val, strlen(val)); +} + +/* Append uint to ds, with optional replace */ +void replace_dynstr_append_uint(DYNAMIC_STRING *ds, uint val) +{ + char buff[22]; /* This should be enough for any int */ + char *end= longlong10_to_str(val, buff, 10); + replace_dynstr_append_mem(ds, buff, end - buff); +} + + +/* + Build a list of pointer to each line in ds_input, sort + the list and use the sorted list to append the strings + sorted to the output ds + + SYNOPSIS + dynstr_append_sorted() + ds string where the sorted output will be appended + ds_input string to be sorted + keep_header If header should not be sorted +*/ + +static int comp_lines(const char **a, const char **b) +{ + return (strcmp(*a,*b)); +} + +void dynstr_append_sorted(DYNAMIC_STRING* ds, DYNAMIC_STRING *ds_input, + bool keep_header) +{ + unsigned i; + char *start= ds_input->str; + DYNAMIC_ARRAY lines; + DBUG_ENTER("dynstr_append_sorted"); + + if (!*start) + DBUG_VOID_RETURN; /* No input */ + + my_init_dynamic_array(PSI_NOT_INSTRUMENTED, &lines, sizeof(const char*), 32, 32, MYF(0)); + + if (keep_header) + { + /* First line is result header, skip past it */ + while (*start && *start != '\n') + start++; + start++; /* Skip past \n */ + dynstr_append_mem(ds, ds_input->str, start - ds_input->str); + } + + /* Insert line(s) in array */ + while (*start) + { + char* line_end= (char*)start; + + /* Find end of line */ + while (*line_end && *line_end != '\n') + line_end++; + *line_end= 0; + + /* Insert pointer to the line in array */ + if (insert_dynamic(&lines, &start)) + die("Out of memory inserting lines to sort"); + + start= line_end+1; + } + + /* Sort array */ + qsort(lines.buffer, lines.elements, + sizeof(uchar *), (qsort_cmp)comp_lines); + + /* Create new result */ + for (i= 0; i < lines.elements ; i++) + { + const char **line= dynamic_element(&lines, i, const char**); + dynstr_append(ds, *line); + dynstr_append(ds, "\n"); + } + + delete_dynamic(&lines); + DBUG_VOID_RETURN; +} + +#ifndef HAVE_SETENV +static int setenv(const char *name, const char *value, int overwrite) +{ + size_t buflen= strlen(name) + strlen(value) + 2; + char *envvar= (char *)malloc(buflen); + if(!envvar) + return ENOMEM; + + snprintf(envvar, buflen, "%s=%s", name, value); + putenv(envvar); + return 0; +} +#endif + +/* + for the purpose of testing (see dialog.test) + we replace default mysql_authentication_dialog_ask function with the one, + that always reads from stdin with explicit echo. + +*/ +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(prompt, stdout); + fputs(" ", stdout); + + if (!fgets(buf, buf_len-1, stdin)) + buf[0]= 0; + else if (buf[0] && (s= strend(buf))[-1] == '\n') + s[-1]= 0; + + for (s= buf; *s; s++) + fputc(type == 2 ? '*' : *s, stdout); + + fputc('\n', stdout); + + return buf; +} + +/* + Enable optimizer trace for the next command +*/ + +LEX_CSTRING enable_optimizer_trace_query= +{ + STRING_WITH_LEN("set @mysqltest_save_optimzer_trace=@@optimizer_trace,@@optimizer_trace=\"enabled=on\"") +}; + +LEX_CSTRING restore_optimizer_trace_query= +{ + STRING_WITH_LEN("set @@optimizer_trace=@mysqltest_save_optimzer_trace") +}; + + +LEX_CSTRING display_optimizer_trace_query +{ + STRING_WITH_LEN("SELECT * from information_schema.optimizer_trace") +}; + + +void enable_optimizer_trace(struct st_connection *con) +{ + MYSQL *mysql= con->mysql; + my_bool save_ps_protocol_enabled= ps_protocol_enabled; + my_bool save_view_protocol_enabled= view_protocol_enabled; + DYNAMIC_STRING ds_result; + DYNAMIC_STRING ds_warnings; + struct st_command command; + DBUG_ENTER("enable_optimizer_trace"); + + if (!mysql) + DBUG_VOID_RETURN; + ps_protocol_enabled= view_protocol_enabled= 0; + + init_dynamic_string(&ds_result, NULL, 0, 256); + init_dynamic_string(&ds_warnings, NULL, 0, 256); + bzero(&command, sizeof(command)); + + run_query_normal(con, &command, QUERY_SEND_FLAG | QUERY_REAP_FLAG, + enable_optimizer_trace_query.str, + enable_optimizer_trace_query.length, + &ds_result, &ds_warnings); + dynstr_free(&ds_result); + dynstr_free(&ds_warnings); + ps_protocol_enabled= save_ps_protocol_enabled; + view_protocol_enabled= save_view_protocol_enabled; + optimizer_trace_active= 1; + DBUG_VOID_RETURN; +} + + +void display_optimizer_trace(struct st_connection *con, + DYNAMIC_STRING *ds) +{ + my_bool save_ps_protocol_enabled= ps_protocol_enabled; + my_bool save_view_protocol_enabled= view_protocol_enabled; + DYNAMIC_STRING ds_result; + DYNAMIC_STRING ds_warnings; + struct st_command command; + DBUG_ENTER("display_optimizer_trace"); + + if (!optimizer_trace_active) + DBUG_VOID_RETURN; + + optimizer_trace_active= 0; + ps_protocol_enabled= view_protocol_enabled= 0; + + init_dynamic_string(&ds_result, NULL, 0, 256); + init_dynamic_string(&ds_warnings, NULL, 0, 256); + bzero(&command, sizeof(command)); + + run_query_normal(con, &command, QUERY_SEND_FLAG | QUERY_REAP_FLAG, + display_optimizer_trace_query.str, + display_optimizer_trace_query.length, + ds, &ds_warnings); + run_query_normal(con, &command, QUERY_SEND_FLAG | QUERY_REAP_FLAG, + restore_optimizer_trace_query.str, + restore_optimizer_trace_query.length, + ds, &ds_warnings); + dynstr_free(&ds_result); + dynstr_free(&ds_warnings); + ps_protocol_enabled= save_ps_protocol_enabled; + view_protocol_enabled= save_view_protocol_enabled; + DBUG_VOID_RETURN; +} diff --git a/client/readline.cc b/client/readline.cc new file mode 100644 index 00000000..e4014658 --- /dev/null +++ b/client/readline.cc @@ -0,0 +1,262 @@ +/* + 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 */ + +#include <my_global.h> +#include <my_sys.h> +#include <m_string.h> +#include <my_dir.h> +#include "my_readline.h" + +static bool init_line_buffer(LINE_BUFFER *buffer,File file,ulong size, + ulong max_size); +static bool init_line_buffer_from_string(LINE_BUFFER *buffer,char * str); +static size_t fill_buffer(LINE_BUFFER *buffer); +static char *intern_read_line(LINE_BUFFER *buffer, ulong *out_length); + + +LINE_BUFFER *batch_readline_init(ulong max_size,FILE *file) +{ + LINE_BUFFER *line_buff; + +#ifndef _WIN32 + MY_STAT input_file_stat; + if (my_fstat(fileno(file), &input_file_stat, MYF(MY_WME)) || + MY_S_ISDIR(input_file_stat.st_mode) || + MY_S_ISBLK(input_file_stat.st_mode)) + return 0; +#endif + + if (!(line_buff=(LINE_BUFFER*) + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*line_buff), + MYF(MY_WME | MY_ZEROFILL)))) + return 0; + if (init_line_buffer(line_buff,my_fileno(file),IO_SIZE,max_size)) + { + my_free(line_buff); + return 0; + } + return line_buff; +} + + +char *batch_readline(LINE_BUFFER *line_buff, bool binary_mode) +{ + char *pos; + ulong UNINIT_VAR(out_length); + + if (!(pos=intern_read_line(line_buff, &out_length))) + return 0; + if (out_length && pos[out_length-1] == '\n') + { + /* Remove '\n' */ + if (--out_length && !binary_mode && pos[out_length-1] == '\r') + /* Remove '\r' */ + out_length--; + } + line_buff->read_length=out_length; + pos[out_length]=0; + return pos; +} + + +void batch_readline_end(LINE_BUFFER *line_buff) +{ + if (line_buff) + { + my_free(line_buff->buffer); + my_free(line_buff); + } +} + + +LINE_BUFFER *batch_readline_command(LINE_BUFFER *line_buff, char * str) +{ + if (!line_buff) + if (!(line_buff=(LINE_BUFFER*) + my_malloc(PSI_NOT_INSTRUMENTED, sizeof(*line_buff), + MYF(MY_WME | MY_ZEROFILL)))) + return 0; + if (init_line_buffer_from_string(line_buff,str)) + { + my_free(line_buff); + return 0; + } + return line_buff; +} + + +/***************************************************************************** + Functions to handle buffered readings of lines from a stream +******************************************************************************/ + +static bool +init_line_buffer(LINE_BUFFER *buffer,File file,ulong size,ulong max_buffer) +{ + buffer->file=file; + buffer->bufread=size; + buffer->max_size=max_buffer; + if (!(buffer->buffer = (char*) my_malloc(PSI_NOT_INSTRUMENTED, + buffer->bufread+1, MYF(MY_WME | MY_FAE)))) + return 1; + buffer->end_of_line=buffer->end=buffer->buffer; + buffer->buffer[0]=0; /* For easy start test */ + return 0; +} + +/* + init_line_buffer_from_string can be called on the same buffer + several times. the resulting buffer will contain a + concatenation of all strings separated by spaces +*/ +static bool init_line_buffer_from_string(LINE_BUFFER *buffer,char * str) +{ + uint old_length=(uint)(buffer->end - buffer->buffer); + uint length= (uint) strlen(str); + if (!(buffer->buffer= buffer->start_of_line= buffer->end_of_line= + (char*) my_realloc(PSI_NOT_INSTRUMENTED, buffer->buffer, + old_length+length+2, MYF(MY_FAE|MY_ALLOW_ZERO_PTR)))) + return 1; + buffer->end= buffer->buffer + old_length; + if (old_length) + buffer->end[-1]=' '; + memcpy(buffer->end, str, length); + buffer->end[length]= '\n'; + buffer->end[length+1]= 0; + buffer->end+= length+1; + buffer->eof=1; + buffer->max_size=1; + return 0; +} + + +/* + Fill the buffer retaining the last n bytes at the beginning of the + newly filled buffer (for backward context). Returns the number of new + bytes read from disk. +*/ + +static size_t fill_buffer(LINE_BUFFER *buffer) +{ + size_t read_count; + uint bufbytes= (uint) (buffer->end - buffer->start_of_line); + + if (buffer->eof) + return 0; /* Everything read */ + + /* See if we need to grow the buffer. */ + + for (;;) + { + uint start_offset=(uint) (buffer->start_of_line - buffer->buffer); + read_count=(buffer->bufread - bufbytes)/IO_SIZE; + if ((read_count*=IO_SIZE)) + break; + if (buffer->bufread * 2 > buffer->max_size) + { + /* + So we must grow the buffer but we cannot due to the max_size limit. + Return 0 w/o setting buffer->eof to signal this condition. + */ + return 0; + } + buffer->bufread *= 2; + if (!(buffer->buffer = (char*) my_realloc(PSI_NOT_INSTRUMENTED, + buffer->buffer, buffer->bufread+1, + MYF(MY_WME | MY_FAE)))) + { + buffer->error= my_errno; + return (size_t) -1; + } + buffer->start_of_line=buffer->buffer+start_offset; + buffer->end=buffer->buffer+bufbytes; + } + + /* Shift stuff down. */ + if (buffer->start_of_line != buffer->buffer) + { + bmove(buffer->buffer,buffer->start_of_line,(uint) bufbytes); + buffer->end=buffer->buffer+bufbytes; + } + + /* Read in new stuff. */ + if ((read_count= my_read(buffer->file, (uchar*) buffer->end, read_count, + MYF(MY_WME))) == MY_FILE_ERROR) + { + buffer->error= my_errno; + return (size_t) -1; + } + + DBUG_PRINT("fill_buff", ("Got %lu bytes", (ulong) read_count)); + + if (!read_count) + { + buffer->eof = 1; + /* Kludge to pretend every nonempty file ends with a newline. */ + if (bufbytes && buffer->end[-1] != '\n') + { + read_count = 1; + *buffer->end = '\n'; + } + } + buffer->end_of_line=(buffer->start_of_line=buffer->buffer)+bufbytes; + buffer->end+=read_count; + *buffer->end=0; /* Sentinel */ + return read_count; +} + + +char *intern_read_line(LINE_BUFFER *buffer, ulong *out_length) +{ + char *pos; + size_t length; + DBUG_ENTER("intern_read_line"); + + buffer->start_of_line=buffer->end_of_line; + for (;;) + { + pos=buffer->end_of_line; + while (*pos != '\n' && pos != buffer->end) + pos++; + if (pos == buffer->end) + { + /* + fill_buffer() can return NULL on EOF (in which case we abort), + on error, or when the internal buffer has hit the size limit. + In the latter case return what we have read so far and signal + string truncation. + */ + if (!(length= fill_buffer(buffer))) + { + if (buffer->eof) + DBUG_RETURN(0); + } + else if (length == (size_t) -1) + DBUG_RETURN(NULL); + else + continue; + pos--; /* break line here */ + buffer->truncated= 1; + } + else + buffer->truncated= 0; + buffer->end_of_line=pos+1; + *out_length=(ulong) (pos + 1 - buffer->eof - buffer->start_of_line); + DBUG_RETURN(buffer->start_of_line); + } +} |